From 22327fd63581d5a70c4a6d87ac86cf6ba3270e03 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Tue, 30 Jan 2024 10:16:37 -0700 Subject: [PATCH 01/38] require: `Debug`s --- arbiter-core/src/data_collection.rs | 12 ++++++++++++ arbiter-engine/src/agent.rs | 13 +++++++++++++ arbiter-engine/src/examples/timed_message.rs | 1 + arbiter-engine/src/lib.rs | 1 + arbiter-engine/src/machine.rs | 5 +++-- arbiter-engine/src/world.rs | 1 + 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/arbiter-core/src/data_collection.rs b/arbiter-core/src/data_collection.rs index e659b3b9a..ed8a63f8e 100644 --- a/arbiter-core/src/data_collection.rs +++ b/arbiter-core/src/data_collection.rs @@ -72,6 +72,18 @@ pub struct EventLogger { metadata: Option, } +impl Debug for EventLogger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EventLogger") + .field("receiver", &self.receiver) + .field("output_file_type", &self.output_file_type) + .field("directory", &self.directory) + .field("file_name", &self.file_name) + .field("metadata", &self.metadata) + .finish() + } +} + /// `OutputFileType` is an enumeration that represents the different types of /// file formats that the `EventLogger` can output to. #[derive(Debug, Clone, Copy, Serialize)] diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index e862e9e00..bdc3f70a6 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -98,6 +98,19 @@ pub struct Agent { broadcast_task: Option + Send>>>>, } +impl Debug for Agent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Agent") + .field("id", &self.id) + .field("state", &self.state) + .field("messager", &self.messager) + .field("client", &self.client) + .field("event_streamer", &self.event_streamer) + .field("behavior_engines", &self.behavior_engines) + .finish() + } +} + impl Agent { /// Produces a new agent with the given identifier. pub fn new(id: &str, world: &World) -> Self { diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index df76c0c84..2ee43a857 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -15,6 +15,7 @@ use crate::{ world::World, }; +#[derive(Debug)] struct TimedMessage { delay: u64, receive_data: String, diff --git a/arbiter-engine/src/lib.rs b/arbiter-engine/src/lib.rs index 1a2869242..2bdee4142 100644 --- a/arbiter-engine/src/lib.rs +++ b/arbiter-engine/src/lib.rs @@ -15,4 +15,5 @@ pub mod agent; pub mod examples; pub mod machine; pub mod messager; +pub mod universe; pub mod world; diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 856f42c97..d8dceeddf 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -80,7 +80,7 @@ pub enum State { /// The [`Behavior`] trait is the lowest level functionality that will be used /// by a [`StateMachine`]. This constitutes what each state transition will do. #[async_trait::async_trait] -pub trait Behavior: Send + Sync + 'static { +pub trait Behavior: Send + Sync + Debug + 'static { /// Used to bring the agent back up to date with the latest state of the /// world. This could be used if the world was stopped and later restarted. async fn sync(&mut self, _messager: Messager, _client: Arc) {} @@ -97,7 +97,7 @@ pub trait Behavior: Send + Sync + 'static { } #[async_trait::async_trait] -pub(crate) trait StateMachine: Send + Sync + 'static { +pub(crate) trait StateMachine: Send + Sync + Debug + 'static { async fn execute(&mut self, instruction: MachineInstruction); } @@ -109,6 +109,7 @@ pub(crate) trait StateMachine: Send + Sync + 'static { /// generics can be collapsed into a `dyn` trait object so that, for example, /// [`agent::Agent`]s can own multiple [`Behavior`]s with different event `` /// types. +#[derive(Debug)] pub struct Engine where B: Behavior, diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index e435e1658..8bc952a18 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -54,6 +54,7 @@ use crate::{ /// 5. [`State::Stopped`]: The [`World`] is stopped. This is where the [`World`] /// can be stopped and state of the [`World`] and its [`Agent`]s can be /// offloaded and saved. +#[derive(Debug)] pub struct World { /// The identifier of the world. pub id: String, From c934d418ff7d970af0637eb30a02d0138d720de8 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Tue, 30 Jan 2024 10:29:15 -0700 Subject: [PATCH 02/38] feat: `Universe` --- arbiter-engine/src/universe.rs | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 arbiter-engine/src/universe.rs diff --git a/arbiter-engine/src/universe.rs b/arbiter-engine/src/universe.rs new file mode 100644 index 000000000..efe427eea --- /dev/null +++ b/arbiter-engine/src/universe.rs @@ -0,0 +1,58 @@ +//! The [`universe`] module contains the [`Universe`] struct which is the +//! primary interface for creating and running many `World`s in parallel. + +use std::collections::HashMap; + +use anyhow::anyhow; + +use crate::world::World; + +/// The [`Universe`] struct is the primary interface for creating and running +/// many `World`s in parallel. At the moment, is a wrapper around a +/// [`HashMap`] of [`World`]s, but can be extended to handle generics inside of +/// [`World`]s and crosstalk between [`World`]s. +#[derive(Debug, Default)] +pub struct Universe { + worlds: Option>, + world_tasks: Option>>, +} + +impl Universe { + pub fn new() -> Self { + Self { + worlds: Some(HashMap::new()), + world_tasks: None, + } + } + + pub fn add_world(&mut self, world: World) { + if let Some(worlds) = self.worlds.as_mut() { + worlds.insert(world.id.clone(), world); + } + } + + pub fn run_worlds(&mut self) { + let mut tasks = HashMap::new(); + for (id, mut world) in self.worlds.take().unwrap().drain() { + tasks.insert(id, tokio::spawn(async move { world.run().await })); + } + } + + pub fn is_online(&self) -> bool { + self.worlds.is_none() && self.world_tasks.is_some() + } + + pub fn is_complete(&self, id: &str) -> anyhow::Result { + if self.is_online() { + let world_task = self + .world_tasks + .as_ref() + .unwrap() + .get(id) + .ok_or(anyhow!("World not found."))?; + Ok(world_task.is_finished()) + } else { + Err(anyhow!("Universe is not online.")) + } + } +} From 19f7d5b9f89cb0cfd99de07caa3133d51ddc42c8 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Tue, 30 Jan 2024 10:37:25 -0700 Subject: [PATCH 03/38] test: `run_universe()` --- arbiter-engine/src/universe.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/arbiter-engine/src/universe.rs b/arbiter-engine/src/universe.rs index efe427eea..4416c2b6f 100644 --- a/arbiter-engine/src/universe.rs +++ b/arbiter-engine/src/universe.rs @@ -36,6 +36,7 @@ impl Universe { for (id, mut world) in self.worlds.take().unwrap().drain() { tasks.insert(id, tokio::spawn(async move { world.run().await })); } + self.world_tasks = Some(tasks); } pub fn is_online(&self) -> bool { @@ -56,3 +57,21 @@ impl Universe { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn run_universe() { + let mut universe = Universe::new(); + let world = World::new("test"); + universe.add_world(world); + universe.run_worlds(); + assert!(universe.is_online()); + assert!(!universe.is_complete("test").unwrap()); + + let task = universe.world_tasks.unwrap().remove("test").unwrap(); + task.await.unwrap(); + } +} From 8114c0f051aab6862529d20a80eb55391655088a Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Tue, 30 Jan 2024 11:21:20 -0700 Subject: [PATCH 04/38] test: initial tests for `Universe` --- arbiter-engine/src/examples/mod.rs | 2 +- arbiter-engine/src/examples/timed_message.rs | 22 +++- arbiter-engine/src/machine.rs | 2 +- arbiter-engine/src/universe.rs | 131 +++++++++++++++---- arbiter-engine/src/world.rs | 3 +- 5 files changed, 125 insertions(+), 35 deletions(-) diff --git a/arbiter-engine/src/examples/mod.rs b/arbiter-engine/src/examples/mod.rs index 5ca2c05e4..12e7fd8c7 100644 --- a/arbiter-engine/src/examples/mod.rs +++ b/arbiter-engine/src/examples/mod.rs @@ -18,5 +18,5 @@ use futures_util::{stream, StreamExt}; use super::*; use crate::messager::{Message, Messager}; -mod timed_message; +pub(crate) mod timed_message; mod token_minter; diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 2ee43a857..b070c9dd6 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -16,13 +16,14 @@ use crate::{ }; #[derive(Debug)] -struct TimedMessage { +pub(crate) struct TimedMessage { delay: u64, receive_data: String, send_data: String, messager: Option, count: u64, max_count: Option, + start_message: Option, } impl TimedMessage { @@ -31,6 +32,7 @@ impl TimedMessage { receive_data: String, send_data: String, max_count: Option, + start_message: Option, ) -> Self { Self { delay, @@ -39,6 +41,7 @@ impl TimedMessage { messager: None, count: 0, max_count, + start_message, } } } @@ -77,6 +80,14 @@ impl Behavior for TimedMessage { async fn startup(&mut self) { trace!("Starting up `TimedMessage`."); + if let Some(start_message) = &self.start_message { + let message = Message { + from: self.messager.as_ref().unwrap().id.clone().unwrap(), + to: To::All, + data: start_message.clone(), + }; + self.messager.as_ref().unwrap().send(message).await; + } tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; trace!("Started up `TimedMessage`."); } @@ -92,6 +103,7 @@ async fn echoer() { "Hello, world!".to_owned(), "Hello, world!".to_owned(), Some(2), + None, ); world.add_agent(agent.with_behavior(behavior)); @@ -130,8 +142,8 @@ async fn ping_pong() { let mut world = World::new("world"); let agent = Agent::new(AGENT_ID, &world); - let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2)); - let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2)); + let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2), None); + let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2), None); world.add_agent( agent .with_behavior(behavior_ping) @@ -174,10 +186,10 @@ async fn ping_pong_two_agent() { let mut world = World::new("world"); let agent_ping = Agent::new("agent_ping", &world); - let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2)); + let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2), None); let agent_pong = Agent::new("agent_pong", &world); - let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2)); + let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2), None); world.add_agent(agent_ping.with_behavior(behavior_ping)); world.add_agent(agent_pong.with_behavior(behavior_pong)); diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index d8dceeddf..87c756c5b 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -46,7 +46,7 @@ pub enum MachineInstruction { pub struct MachineHalt; /// The state used by any entity implementing [`StateMachine`]. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum State { /// The entity is not yet running any process. /// This is the state adopted by the entity when it is first created. diff --git a/arbiter-engine/src/universe.rs b/arbiter-engine/src/universe.rs index 4416c2b6f..d07558b13 100644 --- a/arbiter-engine/src/universe.rs +++ b/arbiter-engine/src/universe.rs @@ -3,7 +3,9 @@ use std::collections::HashMap; -use anyhow::anyhow; +use anyhow::Result; +use futures_util::future::{join, join_all, JoinAll}; +use tokio::task::{spawn, JoinError, JoinHandle}; use crate::world::World; @@ -14,7 +16,7 @@ use crate::world::World; #[derive(Debug, Default)] pub struct Universe { worlds: Option>, - world_tasks: Option>>, + world_tasks: Option>>, } impl Universe { @@ -31,47 +33,122 @@ impl Universe { } } - pub fn run_worlds(&mut self) { - let mut tasks = HashMap::new(); - for (id, mut world) in self.worlds.take().unwrap().drain() { - tasks.insert(id, tokio::spawn(async move { world.run().await })); + pub async fn run_worlds(&mut self) -> Result<()> { + if self.is_online() { + return Err(anyhow::anyhow!("Universe is already running.")); + } + let mut tasks = Vec::new(); + for (_, world) in self.worlds.take().unwrap().drain() { + tasks.push(spawn(async move { world.run().await })); } - self.world_tasks = Some(tasks); + self.world_tasks = Some(join_all(tasks.into_iter()).await); + Ok(()) } pub fn is_online(&self) -> bool { - self.worlds.is_none() && self.world_tasks.is_some() - } - - pub fn is_complete(&self, id: &str) -> anyhow::Result { - if self.is_online() { - let world_task = self - .world_tasks - .as_ref() - .unwrap() - .get(id) - .ok_or(anyhow!("World not found."))?; - Ok(world_task.is_finished()) - } else { - Err(anyhow!("Universe is not online.")) - } + self.world_tasks.is_some() } } #[cfg(test)] mod tests { + use std::fs::{read_to_string, remove_file, File}; + + use tracing_subscriber::{fmt, EnvFilter}; + use super::*; + use crate::{agent::Agent, examples::timed_message::*, machine::State}; #[tokio::test] async fn run_universe() { let mut universe = Universe::new(); let world = World::new("test"); universe.add_world(world); - universe.run_worlds(); - assert!(universe.is_online()); - assert!(!universe.is_complete("test").unwrap()); + universe.run_worlds().await.unwrap(); + let world = universe.world_tasks.unwrap().remove(0).unwrap(); + assert_eq!(world.state, State::Processing); + } + + #[tokio::test] + #[should_panic(expected = "Universe is already running.")] + async fn cant_run_twice() { + let mut universe = Universe::new(); + let world1 = World::new("test"); + universe.add_world(world1); + universe.run_worlds().await.unwrap(); + universe.run_worlds().await.unwrap(); + } + + #[tokio::test] + async fn run_parallel() { + std::env::set_var("RUST_LOG", "trace"); + let file = File::create("test_logs_engine.log").expect("Unable to create log file"); + + let subscriber = fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_writer(file) + .finish(); + + tracing::subscriber::set_global_default(subscriber) + .expect("setting default subscriber failed"); + + let mut world1 = World::new("test1"); + let agent1 = Agent::new("agent1", &world1); + let behavior1 = TimedMessage::new( + 1, + "echo".to_owned(), + "echo".to_owned(), + Some(5), + Some("echo".to_owned()), + ); + world1.add_agent(agent1.with_behavior(behavior1)); + + let mut world2 = World::new("test2"); + let agent2 = Agent::new("agent2", &world2); + let behavior2 = TimedMessage::new( + 1, + "echo".to_owned(), + "echo".to_owned(), + Some(5), + Some("echo".to_owned()), + ); + world2.add_agent(agent2.with_behavior(behavior2)); + + let mut universe = Universe::new(); + universe.add_world(world1); + universe.add_world(world2); + + universe.run_worlds().await.unwrap(); + + let parsed_file = read_to_string("test_logs_engine.log").expect("Unable to read log file"); + + // Define the line to check (excluding the timestamp) + let line_to_check = "World is syncing."; + + // Assert that the lines appear consecutively + assert!( + lines_appear_consecutively(&parsed_file, line_to_check), + "The lines do not appear consecutively" + ); + remove_file("test_logs_engine.log").expect("Unable to remove log file"); + } + + fn lines_appear_consecutively(file_contents: &str, line_to_check: &str) -> bool { + let mut lines = file_contents.lines(); + + while let Some(line) = lines.next() { + if line.contains(line_to_check) { + println!("Found line: {}", line); + // Check if the next line also contains the line_to_check + if let Some(next_line) = lines.next() { + if next_line.contains(line_to_check) { + println!("Found next line: {}", next_line); + return true; + } + } + } + } - let task = universe.world_tasks.unwrap().remove("test").unwrap(); - task.await.unwrap(); + false } } diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 8bc952a18..fd2bd94a8 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -107,10 +107,11 @@ impl World { } /// Runs the world through up to the [`State::Processing`] stage. - pub async fn run(&mut self) { + pub async fn run(mut self) -> Self { self.execute(MachineInstruction::Sync(None, None)).await; self.execute(MachineInstruction::Start).await; self.execute(MachineInstruction::Process).await; + self } /// Stops the world by stopping all the behaviors that each of the agents is From 80541063797e1ea6ae12f23cd18be7f6e6c32847 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Tue, 30 Jan 2024 11:23:25 -0700 Subject: [PATCH 05/38] =?UTF-8?q?fix:=20lint=20=F0=9F=A7=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arbiter-engine/src/universe.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arbiter-engine/src/universe.rs b/arbiter-engine/src/universe.rs index d07558b13..ca77fb0ed 100644 --- a/arbiter-engine/src/universe.rs +++ b/arbiter-engine/src/universe.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use anyhow::Result; -use futures_util::future::{join, join_all, JoinAll}; -use tokio::task::{spawn, JoinError, JoinHandle}; +use futures_util::future::join_all; +use tokio::task::{spawn, JoinError}; use crate::world::World; @@ -20,6 +20,7 @@ pub struct Universe { } impl Universe { + /// Creates a new [`Universe`]. pub fn new() -> Self { Self { worlds: Some(HashMap::new()), @@ -27,12 +28,14 @@ impl Universe { } } + /// Adds a [`World`] to the [`Universe`]. pub fn add_world(&mut self, world: World) { if let Some(worlds) = self.worlds.as_mut() { worlds.insert(world.id.clone(), world); } } + /// Runs all of the [`World`]s in the [`Universe`] in parallel. pub async fn run_worlds(&mut self) -> Result<()> { if self.is_online() { return Err(anyhow::anyhow!("Universe is already running.")); @@ -45,6 +48,7 @@ impl Universe { Ok(()) } + /// Returns `true` if the [`Universe`] is running. pub fn is_online(&self) -> bool { self.world_tasks.is_some() } From 20eb89892d3d5a8467e27bdebaeef9ff81d85b31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:50:53 +0000 Subject: [PATCH 06/38] build(deps): bump toml from 0.8.8 to 0.8.9 Bumps [toml](https://github.com/toml-rs/toml) from 0.8.8 to 0.8.9. - [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.8...toml-v0.8.9) --- updated-dependencies: - dependency-name: toml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 18 +++++++++--------- Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9be3130d..1ce08ae68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "toml 0.8.8", + "toml 0.8.9", ] [[package]] @@ -1699,7 +1699,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.43", - "toml 0.8.8", + "toml 0.8.9", "walkdir", ] @@ -1948,7 +1948,7 @@ dependencies = [ "atomic", "pear", "serde", - "toml 0.8.8", + "toml 0.8.9", "uncased", "version_check", ] @@ -3973,7 +3973,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.21.0", + "toml_edit 0.21.1", ] [[package]] @@ -5481,14 +5481,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.21.1", ] [[package]] @@ -5526,9 +5526,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "serde", diff --git a/Cargo.toml b/Cargo.toml index ec37abee8..64581579f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ serde_json.workspace = true config = { version = "=0.13.4" } ethers.workspace = true revm.workspace = true -toml = { version = "=0.8.8" } +toml = { version = "=0.8.9" } proc-macro2.workspace = true syn.workspace = true Inflector = { version = "=0.11.4" } From 1cb4ce1aca8963ed2317bd8d2e19ae3623479bab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:51:21 +0000 Subject: [PATCH 07/38] build(deps): bump ethers from 2.0.11 to 2.0.13 Bumps [ethers](https://github.com/gakonst/ethers-rs) from 2.0.11 to 2.0.13. - [Release notes](https://github.com/gakonst/ethers-rs/releases) - [Changelog](https://github.com/gakonst/ethers-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/gakonst/ethers-rs/compare/ethers-v2.0.11...ethers-v2.0.13) --- updated-dependencies: - dependency-name: ethers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 44 ++++++++++++++++++++++---------------------- Cargo.toml | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9be3130d..fdf4ea333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1634,9 +1634,9 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.11" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5344eea9b20effb5efeaad29418215c4d27017639fd1f908260f59cbbd226e" +checksum = "6c7cd562832e2ff584fa844cd2f6e5d4f35bbe11b28c7c9b8df957b2e1d0c701" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -1650,9 +1650,9 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf35eb7d2e2092ad41f584951e08ec7c077b142dba29c4f1b8f52d2efddc49c" +checksum = "35dc9a249c066d17e8947ff52a4116406163cf92c7f0763cb8c001760b26403f" dependencies = [ "ethers-core", "once_cell", @@ -1662,9 +1662,9 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.11" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0111ead599d17a7bff6985fd5756f39ca7033edc79a31b23026a8d5d64fa95cd" +checksum = "43304317c7f776876e47f2f637859f6d0701c1ec7930a150f169d5fbe7d76f5a" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -1681,9 +1681,9 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbdfb952aafd385b31d316ed80d7b76215ce09743c172966d840e96924427e0c" +checksum = "f9f96502317bf34f6d71a3e3d270defaa9485d754d789e15a8e04a84161c95eb" dependencies = [ "Inflector", "const-hex", @@ -1705,9 +1705,9 @@ dependencies = [ [[package]] name = "ethers-contract-derive" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7465c814a2ecd0de0442160da13584205d1cdc08f4717a6511cad455bd5d7dc4" +checksum = "452ff6b0a64507ce8d67ffd48b1da3b42f03680dcf5382244e9c93822cbbf5de" dependencies = [ "Inflector", "const-hex", @@ -1721,9 +1721,9 @@ dependencies = [ [[package]] name = "ethers-core" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918b1a9ba585ea61022647def2f27c29ba19f6d2a4a4c8f68a9ae97fd5769737" +checksum = "aab3cef6cc1c9fd7f787043c81ad3052eff2b96a3878ef1526aa446311bdbfc9" dependencies = [ "arrayvec", "bytes", @@ -1751,9 +1751,9 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "facabf8551b4d1a3c08cb935e7fca187804b6c2525cc0dafb8e5a6dd453a24de" +checksum = "16d45b981f5fa769e1d0343ebc2a44cfa88c9bc312eb681b676318b40cef6fb1" dependencies = [ "chrono", "ethers-core", @@ -1767,9 +1767,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.11" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681ece6eb1d10f7cf4f873059a77c04ff1de4f35c63dd7bccde8f438374fcb93" +checksum = "145211f34342487ef83a597c1e69f0d3e01512217a7c72cc8a25931854c7dca0" dependencies = [ "async-trait", "auto_impl", @@ -1794,9 +1794,9 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.11" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d6c0c9455d93d4990c06e049abf9b30daf148cf461ee939c11d88907c60816" +checksum = "fb6b15393996e3b8a78ef1332d6483c11d839042c17be58decc92fa8b1c3508a" dependencies = [ "async-trait", "auto_impl", @@ -1832,9 +1832,9 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.11" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb1b714e227bbd2d8c53528adb580b203009728b17d0d0e4119353aa9bc5532" +checksum = "b3b125a103b56aef008af5d5fb48191984aa326b50bfd2557d231dc499833de3" dependencies = [ "async-trait", "coins-bip32", @@ -1851,9 +1851,9 @@ dependencies = [ [[package]] name = "ethers-solc" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2e46e3ec8ef0c986145901fa9864205dc4dcee701f9846be2d56112d34bdea" +checksum = "d21df08582e0a43005018a858cc9b465c5fff9cf4056651be64f844e57d1f55f" dependencies = [ "cfg-if", "const-hex", diff --git a/Cargo.toml b/Cargo.toml index ec37abee8..66b087c19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ path = "bin/main.rs" [workspace.dependencies] arbiter-bindings = { version = "*", path = "./arbiter-bindings" } arbiter-core = { version = "*", path = "./arbiter-core" } -ethers = { version = "2.0.11" } +ethers = { version = "2.0.13" } serde = { version = "1.0.193", features = ["derive"] } serde_json = { version = "=1.0.108" } revm = { git = "https://github.com/bluealloy/revm.git", features = [ "ethersdb", "std", "serde"], rev = "30bbcdfe81446c9d1e9b37acc95f208943ddf858" } From 47e8820c4c6e43e52cbfaa06e2c526b544207daf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 21:18:54 +0000 Subject: [PATCH 08/38] build(deps): bump shlex from 1.2.0 to 1.3.0 Bumps [shlex](https://github.com/comex/rust-shlex) from 1.2.0 to 1.3.0. - [Changelog](https://github.com/comex/rust-shlex/blob/master/CHANGELOG.md) - [Commits](https://github.com/comex/rust-shlex/commits) --- updated-dependencies: - dependency-name: shlex dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2964c87a..f4a0d37ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4852,9 +4852,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" From 2d2c151aae96532cf0a86f307c88a3be73b6bf79 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Wed, 31 Jan 2024 18:30:54 -0500 Subject: [PATCH 09/38] feat: add agent builder --- .rustfmt.toml | 3 +- arbiter-engine/src/agent.rs | 114 ++++++++++++++----- arbiter-engine/src/examples/timed_message.rs | 19 ++-- arbiter-engine/src/examples/token_minter.rs | 43 +++---- arbiter-engine/src/world.rs | 17 ++- 5 files changed, 122 insertions(+), 74 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 10d8a7e27..a148c11f7 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -9,4 +9,5 @@ use_field_init_shorthand = true wrap_comments = true normalize_comments = true -comment_width = 80 \ No newline at end of file +comment_width = 80 +edition = "2021" diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index e862e9e00..083d4b687 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -68,6 +68,15 @@ use crate::{ /// 5. [`State::Stopped`]: The [`Agent`] is stopped. This is where the [`Agent`] /// can be stopped and state of the [`World`] and its [`Agent`]s can be /// offloaded and saved. +// todo(matt): use builder pattern where we just have the agent builder implement deserialize with +// just behavior_engines +// +// #[derive(Serialize, Deserialize)] +// pub struct AgentBuilder { +// pub id: String, +// pub behavior_engines: Option>>, +// pub world: &World +// } pub struct Agent { /// Identifier for this agent. /// Used for routing messages. @@ -89,7 +98,7 @@ pub struct Agent { /// The engines/behaviors that the agent uses to sync, startup, and process /// events. - behavior_engines: Option>>, + behavior_engines: Vec>, /// The pipeline for yielding events from the centralized event streamer /// (for both messages and Ethereum events) to agents. @@ -99,21 +108,14 @@ pub struct Agent { } impl Agent { - /// Produces a new agent with the given identifier. - pub fn new(id: &str, world: &World) -> Self { - let messager = world.messager.for_agent(id); - let client = RevmMiddleware::new(&world.environment, Some(id)).unwrap(); + /// Produces a minimal agent builder with the given identifier. + pub fn builder(id: &str) -> Result { let distributor = channel(512); - Self { + Ok(AgentBuilder { id: id.to_owned(), - state: State::Uninitialized, - messager: Some(messager), - client, - event_streamer: Some(EventLogger::builder()), - behavior_engines: None, distributor, - broadcast_task: None, - } + behavior_engines: None, + }) } /// Adds an Ethereum event to the agent's event streamer. @@ -125,7 +127,52 @@ impl Agent { self } - /// Adds a behavior to the agent that it will run. + pub(crate) async fn run(&mut self, instruction: MachineInstruction) { + let behavior_tasks = join_all(self.behavior_engines.drain(..).map(|mut engine| { + let instruction_clone = instruction.clone(); + tokio::spawn(async move { + engine.execute(instruction_clone).await; + engine + }) + })); + self.behavior_engines = behavior_tasks + .await + .into_iter() + .map(|res| res.unwrap()) + .collect::>(); + } +} + +/// enum representing the possible error states encountered by the agent builder +#[derive(Debug)] +pub enum AgentBuildError { + MissingBehaviorEngines, +} + +impl std::error::Error for AgentBuildError {} +impl std::fmt::Display for AgentBuildError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AgentBuildError::MissingBehaviorEngines => { + write!(f, "Behavior engines must be set before building the agent") + } // ... other error variants + } + } +} + +pub struct AgentBuilder { + /// Identifier for this agent. + /// Used for routing messages. + pub id: String, + /// The engines/behaviors that the agent uses to sync, startup, and process + /// events. + behavior_engines: Option>>, + /// The pipeline for yielding events from the centralized event streamer + /// (for both messages and Ethereum events) to agents. + pub(crate) distributor: (BroadcastSender, BroadcastReceiver), +} + +impl AgentBuilder { pub fn with_behavior( mut self, behavior: impl Behavior + 'static, @@ -141,22 +188,25 @@ impl Agent { self } - pub(crate) async fn run(&mut self, instruction: MachineInstruction) { - let behavior_engines = self.behavior_engines.take().unwrap(); - let behavior_tasks = join_all(behavior_engines.into_iter().map(|mut engine| { - let instruction_clone = instruction.clone(); - tokio::spawn(async move { - engine.execute(instruction_clone).await; - engine - }) - })); - self.behavior_engines = Some( - behavior_tasks - .await - .into_iter() - .map(|res| res.unwrap()) - .collect::>(), - ); + /// Produces a new agent with the given identifier. + pub fn build( + self, + messager: Messager, + client: Arc, + ) -> Result { + match self.behavior_engines { + Some(engines) => Ok(Agent { + id: self.id, + state: State::Uninitialized, + messager: Some(messager), + client, + event_streamer: Some(EventLogger::builder()), + behavior_engines: engines, + distributor: self.distributor, + broadcast_task: None, + }), + None => Err(AgentBuildError::MissingBehaviorEngines), + } } } @@ -233,7 +283,8 @@ mod tests { // tracing_subscriber::fmt::init(); let world = World::new("world"); - let agent = Agent::new("agent", &world); + let agent = Agent::builder("agent").unwrap(); + /* let arb = ArbiterToken::deploy( agent.client.clone(), @@ -312,5 +363,6 @@ mod tests { } }); join_all(vec![message_task, eth_event_task, print_task]).await; + */ } } diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index df76c0c84..925b5fcce 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -85,7 +85,7 @@ impl Behavior for TimedMessage { async fn echoer() { let mut world = World::new("world"); - let agent = Agent::new(AGENT_ID, &world); + let agent = Agent::builder(AGENT_ID).unwrap(); let behavior = TimedMessage::new( 1, "Hello, world!".to_owned(), @@ -128,7 +128,7 @@ async fn echoer() { async fn ping_pong() { let mut world = World::new("world"); - let agent = Agent::new(AGENT_ID, &world); + let agent = Agent::builder(AGENT_ID).unwrap(); let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2)); let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2)); world.add_agent( @@ -172,14 +172,17 @@ async fn ping_pong() { async fn ping_pong_two_agent() { let mut world = World::new("world"); - let agent_ping = Agent::new("agent_ping", &world); let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2)); - - let agent_pong = Agent::new("agent_pong", &world); let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2)); - - world.add_agent(agent_ping.with_behavior(behavior_ping)); - world.add_agent(agent_pong.with_behavior(behavior_pong)); + let agent_ping = Agent::builder("agent_ping") + .unwrap() + .with_behavior(behavior_ping); + let agent_pong = Agent::builder("agent_pong") + .unwrap() + .with_behavior(behavior_pong); + + world.add_agent(agent_ping); + world.add_agent(agent_pong); let messager = world.messager.join_with_id(Some("god".to_owned())); let task = world.run(); diff --git a/arbiter-engine/src/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs index 230bacae7..d6d064f06 100644 --- a/arbiter-engine/src/examples/token_minter.rs +++ b/arbiter-engine/src/examples/token_minter.rs @@ -192,12 +192,7 @@ pub struct TokenRequester { } impl TokenRequester { - pub fn new( - client: Arc, - messager: Messager, - count: u64, - max_count: Option, - ) -> Self { + pub fn new(count: u64, max_count: Option) -> Self { Self { token_data: TokenData { name: TOKEN_NAME.to_owned(), @@ -264,7 +259,8 @@ token: {:?}", Some(MachineHalt) } } - +// world.run() -> sync state -> iterate over agents, `execute` each agent -> agent sync state -> +// iterate over behaviors -> behavior sync state #[async_trait::async_trait] impl Behavior for TokenRequester { async fn sync(&mut self, messager: Messager, client: Arc) { @@ -301,13 +297,15 @@ impl Behavior for TokenRequester { } } -#[ignore] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn token_minter_simulation() { + // 3. have a method on world to update mutable map of addresses let mut world = World::new("test_world"); + //self.contracts: HashMap // Create the token admin agent - let token_admin = Agent::new(TOKEN_ADMIN_ID, &world); + // 1. use agent builder struct to get rid of reference to world + let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); let mut token_admin_behavior = TokenAdmin::new(0, Some(4)); token_admin_behavior.add_token(TokenData { name: TOKEN_NAME.to_owned(), @@ -318,33 +316,17 @@ async fn token_minter_simulation() { world.add_agent(token_admin.with_behavior(token_admin_behavior)); // Create the token requester agent - let token_requester = Agent::new(REQUESTER_ID, &world); - let token_requester_behavior = TokenRequester::new( - token_requester.client.clone(), - token_requester - .messager - .as_ref() - .unwrap() - .join_with_id(Some(REQUESTER_ID.to_owned())), - 0, - Some(4), - ); + let token_requester = Agent::builder(REQUESTER_ID).unwrap(); + let token_requester_behavior = TokenRequester::new(0, Some(4)); + // 2. appropriately handle event driven behaviors + /* let arb = ArbiterToken::new( Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), token_requester.client.clone(), ); let transfer_event = arb.transfer_filter(); - let token_requester_behavior_again = TokenRequester::new( - token_requester.client.clone(), - token_requester - .messager - .as_ref() - .unwrap() - .join_with_id(Some(REQUESTER_ID.to_owned())), - 0, - Some(4), - ); + let token_requester_behavior_again = TokenRequester::new(0, Some(4)); world.add_agent( token_requester .with_behavior::(token_requester_behavior) @@ -375,4 +357,5 @@ async fn token_minter_simulation() { } } } + */ } diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index e435e1658..30cf112f3 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -15,12 +15,18 @@ //! The world module contains the core world abstraction for the Arbiter Engine. -use arbiter_core::environment::{Environment, EnvironmentBuilder}; +use arbiter_core::{ + environment::{Environment, EnvironmentBuilder}, + middleware::RevmMiddleware, +}; use futures_util::future::join_all; use tokio::sync::broadcast::Sender as BroadcastSender; use tracing::info; -use self::machine::{MachineHalt, MachineInstruction}; +use self::{ + agent::AgentBuilder, + machine::{MachineHalt, MachineInstruction}, +}; use super::*; use crate::{ agent::Agent, @@ -99,8 +105,11 @@ impl World { } /// Adds an agent to the world. - pub fn add_agent(&mut self, agent: Agent) { - let id = agent.id.clone(); + pub fn add_agent(&mut self, agent_builder: AgentBuilder) { + let id = agent_builder.id.clone(); + let messager = self.messager.for_agent(&id); + let client = RevmMiddleware::new(&self.environment, Some(&id)).unwrap(); + let agent = agent_builder.build(messager, client).unwrap(); let agents = self.agents.as_mut().unwrap(); agents.insert(id.to_owned(), agent); } From e50192ec99af5174add0233d23585d03dccca7e4 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 1 Feb 2024 11:46:16 -0500 Subject: [PATCH 10/38] refactor: updated builder api for environment --- arbiter-core/benches/bench.rs | 2 +- arbiter-core/src/coprocessor.rs | 2 +- arbiter-core/src/environment/mod.rs | 24 +++++++------------ arbiter-core/src/environment/tests.rs | 2 +- arbiter-core/src/middleware/mod.rs | 4 ++-- arbiter-core/src/tests/contracts.rs | 2 +- .../src/tests/environment_integration.rs | 4 ++-- .../src/tests/middleware_integration.rs | 4 ++-- arbiter-core/src/tests/mod.rs | 2 +- arbiter-engine/src/world.rs | 2 +- .../src/usage/arbiter_core/environment.md | 18 +++++++------- .../src/usage/arbiter_core/middleware.md | 8 +++---- 12 files changed, 34 insertions(+), 40 deletions(-) diff --git a/arbiter-core/benches/bench.rs b/arbiter-core/benches/bench.rs index e47b6d09e..75e218e0d 100644 --- a/arbiter-core/benches/bench.rs +++ b/arbiter-core/benches/bench.rs @@ -178,7 +178,7 @@ async fn anvil_startup() -> Result<( } fn arbiter_startup() -> Result<(Environment, Arc)> { - let environment = EnvironmentBuilder::new().build(); + let environment = Environment::builder().build(); let client = RevmMiddleware::new(&environment, Some("name"))?; Ok((environment, client)) diff --git a/arbiter-core/src/coprocessor.rs b/arbiter-core/src/coprocessor.rs index 72c09bf96..7a35d3166 100644 --- a/arbiter-core/src/coprocessor.rs +++ b/arbiter-core/src/coprocessor.rs @@ -53,7 +53,7 @@ mod tests { #[test] fn coprocessor() { - let environment = EnvironmentBuilder::new().build(); + let environment = Environment::builder().build(); let mut coprocessor = Coprocessor::new(&environment); coprocessor.evm.env.tx.value = U256::from(100); let outcome = coprocessor.transact_ref(); diff --git a/arbiter-core/src/environment/mod.rs b/arbiter-core/src/environment/mod.rs index 46734624b..9ebb705d1 100644 --- a/arbiter-core/src/environment/mod.rs +++ b/arbiter-core/src/environment/mod.rs @@ -176,22 +176,7 @@ pub struct EnvironmentBuilder { db: Option, } -impl Default for EnvironmentBuilder { - fn default() -> Self { - Self::new() - } -} - impl EnvironmentBuilder { - /// Creates a new [`EnvironmentBuilder`] with default parameters that can be - /// used to build an [`Environment`]. - pub fn new() -> Self { - Self { - parameters: EnvironmentParameters::default(), - db: None, - } - } - /// Builds and runs an [`Environment`] with the parameters set in the /// [`EnvironmentBuilder`]. pub fn build(self) -> Environment { @@ -239,6 +224,15 @@ impl EnvironmentBuilder { } impl Environment { + /// Creates a new [`EnvironmentBuilder`] with default parameters that can be + /// used to build an [`Environment`]. + pub fn builder() -> EnvironmentBuilder { + EnvironmentBuilder { + parameters: EnvironmentParameters::default(), + db: None, + } + } + fn create(parameters: EnvironmentParameters, db: Option) -> Self { let (instruction_sender, instruction_receiver) = unbounded(); let (event_broadcaster, _) = channel(512); diff --git a/arbiter-core/src/environment/tests.rs b/arbiter-core/src/environment/tests.rs index 0f3419071..b4e9696a2 100644 --- a/arbiter-core/src/environment/tests.rs +++ b/arbiter-core/src/environment/tests.rs @@ -6,7 +6,7 @@ const TEST_GAS_LIMIT: u64 = 1_333_333_333_337; #[test] fn new_with_parameters() { - let environment = EnvironmentBuilder::new() + let environment = Environment::builder() .with_label(TEST_ENV_LABEL) .with_contract_size_limit(TEST_CONTRACT_SIZE_LIMIT) .with_gas_limit(U256::from(TEST_GAS_LIMIT)); diff --git a/arbiter-core/src/middleware/mod.rs b/arbiter-core/src/middleware/mod.rs index 9c83c1078..63774257b 100644 --- a/arbiter-core/src/middleware/mod.rs +++ b/arbiter-core/src/middleware/mod.rs @@ -86,7 +86,7 @@ pub mod nonce_middleware; /// use arbiter_core::{environment::EnvironmentBuilder, middleware::RevmMiddleware}; /// /// // Create a new environment and run it -/// let mut environment = EnvironmentBuilder::new().build(); +/// let mut environment = Environment::builder().build(); /// /// // Retrieve the environment to create a new middleware instance /// let middleware = RevmMiddleware::new(&environment, Some("test_label")); @@ -245,7 +245,7 @@ impl RevmMiddleware { /// use arbiter_core::{environment::EnvironmentBuilder, middleware::RevmMiddleware}; /// /// // Create a new environment and run it - /// let mut environment = EnvironmentBuilder::new().build(); + /// let mut environment = Environment::builder().build(); /// /// // Retrieve the environment to create a new middleware instance /// let client = RevmMiddleware::new(&environment, Some("test_label")); diff --git a/arbiter-core/src/tests/contracts.rs b/arbiter-core/src/tests/contracts.rs index bec7097dc..d845a52e8 100644 --- a/arbiter-core/src/tests/contracts.rs +++ b/arbiter-core/src/tests/contracts.rs @@ -323,7 +323,7 @@ async fn can_log() { tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); // tracing_subscriber::fmt::init(); - let env = EnvironmentBuilder::new().with_console_logs().build(); + let env = Environment::builder().with_console_logs().build(); let client = RevmMiddleware::new(&env, None).unwrap(); let counter = arbiter_bindings::bindings::counter::Counter::deploy(client, ()) .unwrap() diff --git a/arbiter-core/src/tests/environment_integration.rs b/arbiter-core/src/tests/environment_integration.rs index 5a63f9d0f..8542bf7cf 100644 --- a/arbiter-core/src/tests/environment_integration.rs +++ b/arbiter-core/src/tests/environment_integration.rs @@ -85,7 +85,7 @@ async fn fork_into_arbiter() { let fork = Fork::from_disk("../example_fork/fork_into_test.json").unwrap(); // Get the environment going - let environment = EnvironmentBuilder::new().with_db(fork.db).build(); + let environment = Environment::builder().with_db(fork.db).build(); // Create a client let client = RevmMiddleware::new(&environment, Some("name")).unwrap(); @@ -117,7 +117,7 @@ async fn middleware_from_forked_eo() { let fork = Fork::from_disk("../example_fork/fork_into_test.json").unwrap(); // Get the environment going - let environment = EnvironmentBuilder::new().with_db(fork.db).build(); + let environment = Environment::builder().with_db(fork.db).build(); let vitalik_address = fork.eoa.get("vitalik").unwrap(); let vitalik_as_a_client = RevmMiddleware::new_from_forked_eoa(&environment, *vitalik_address); diff --git a/arbiter-core/src/tests/middleware_integration.rs b/arbiter-core/src/tests/middleware_integration.rs index 45030febe..4f4b893ec 100644 --- a/arbiter-core/src/tests/middleware_integration.rs +++ b/arbiter-core/src/tests/middleware_integration.rs @@ -516,7 +516,7 @@ fn simulation_signer() -> Result<()> { #[test] fn multiple_signer_addresses() { - let environment = EnvironmentBuilder::new().build(); + let environment = Environment::builder().build(); let client_1 = RevmMiddleware::new(&environment, Some("0")).unwrap(); let client_2 = RevmMiddleware::new(&environment, Some("1")).unwrap(); assert_ne!(client_1.address(), client_2.address()); @@ -524,7 +524,7 @@ fn multiple_signer_addresses() { #[test] fn signer_collision() { - let environment = EnvironmentBuilder::new().build(); + let environment = Environment::builder().build(); RevmMiddleware::new(&environment, Some("0")).unwrap(); assert!(RevmMiddleware::new(&environment, Some("0")).is_err()); } diff --git a/arbiter-core/src/tests/mod.rs b/arbiter-core/src/tests/mod.rs index 53b767b1f..daa42f7a0 100644 --- a/arbiter-core/src/tests/mod.rs +++ b/arbiter-core/src/tests/mod.rs @@ -49,7 +49,7 @@ pub const ARBITER_TOKEN_Y_DECIMALS: u8 = 18; pub const LIQUID_EXCHANGE_PRICE: f64 = 420.69; fn startup() -> Result<(Environment, Arc)> { - let env = EnvironmentBuilder::new().build(); + let env = Environment::builder().build(); let client = RevmMiddleware::new(&env, Some(TEST_SIGNER_SEED_AND_LABEL))?; Ok((env, client)) } diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 30cf112f3..548bb6325 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -87,7 +87,7 @@ impl World { state: State::Uninitialized, agents: Some(HashMap::new()), agent_distributors: None, - environment: EnvironmentBuilder::new().build(), + environment: Environment::builder().build(), messager: Messager::new(), } } diff --git a/documentation/src/usage/arbiter_core/environment.md b/documentation/src/usage/arbiter_core/environment.md index 283e0904d..2bf23d9af 100644 --- a/documentation/src/usage/arbiter_core/environment.md +++ b/documentation/src/usage/arbiter_core/environment.md @@ -7,10 +7,10 @@ The `Socket` is a struct owned by the `Environment` that manages all inward and To create an `Environment`, we use a builder pattern that allows you to pre-load an `Environment` with your own database. We can do the following to create a default `Environment`: ```rust -use arbiter_core::environment::EnvironmentBuilder; +use arbiter_core::environment::Environment; fn main() { - let env = EnvironmentBuilder::new().build(); + let env = Environment::builder().build(); } ``` Note that the call to `.build()` will start the `Environment`'s thread and begin processing `Instruction`s. @@ -19,10 +19,10 @@ Note that the call to `.build()` will start the `Environment`'s thread and begin The `Environment` also supports the ability to inspect the `revm` instance's state at any point in time which can be useful for debugging and managing gas. By default, the `Environment` will not inspect the `revm` instance's state at all (which should provide the highest speed), but you can enable these features by doing the following: ```rust -use arbiter_core::environment::EnvironmentBuilder; +use arbiter_core::environment::Environment; fn main() { - let env = EnvironmentBuilder::new() + let env = Environment::builder() .with_console_logs() .with_pay_gas() .build(); @@ -35,13 +35,13 @@ The feature `with_pay_gas` will pay gas for transactions which is useful for rea If you have a database that has been forked from a live network, it has likely been serialized to disk. In which case, you can do something like this: ```rust, ignore -use arbiter_core::environment::EnvironmentBuilder; +use arbiter_core::environment::Environment; use arbiter_core::environment::fork::Fork; fn main() { let path_to_fork = "path/to/fork"; let fork = Fork::from_disk(path_to_fork).unwrap(); - let env = EnvironmentBuilder::new().with_db(fork).build(); + let env = Environment::builder().with_db(fork).build(); } ``` This will create an `Environment` that has been forked from the database at the given path and is ready to receive `Instruction`s. @@ -49,10 +49,10 @@ This will create an `Environment` that has been forked from the database at the `Environment` supports more customization for the `gas_limit` and `contract_size_limit` of the `revm` instance. You can do the following: ```rust -use arbiter_core::environment::EnvironmentBuilder; +use arbiter_core::environment::Environment; fn main() { - let env = EnvironmentBuilder::new() + let env = Environment::builder() .with_gas_limit(revm::primitives::U256::from(12_345_678)) .with_contract_size_limit(111_111) .build(); @@ -83,4 +83,4 @@ The `RevmMiddleware` provides methods for sending the above instructions to an a ## Events The `Environment` also emits Ethereum events and errors/reverts to clients who are set to listen to them. To do so, we use a `tokio::sync::broadcast` channel and the `RevmMiddleware` manages subscriptions to these events. -As for errors or reverts, we are working on making the flow of handling these more graceful so that your own program or agents can decide how to handle them. \ No newline at end of file +As for errors or reverts, we are working on making the flow of handling these more graceful so that your own program or agents can decide how to handle them. diff --git a/documentation/src/usage/arbiter_core/middleware.md b/documentation/src/usage/arbiter_core/middleware.md index b2e715867..6ff029340 100644 --- a/documentation/src/usage/arbiter_core/middleware.md +++ b/documentation/src/usage/arbiter_core/middleware.md @@ -18,10 +18,10 @@ Fortunately, for almost every usecase of `RevmMiddleware`, you will not need to To create a `RevmMiddleware` that is associated with an account in the `Environment`'s world state, we can do the following: ```rust use arbiter_core::middleware::RevmMiddleware; -use arbiter_core::environment::EnvironmentBuilder; +use arbiter_core::environment::Environment; fn main() { - let env = EnvironmentBuilder::new().build(); + let env = Environment::builder().build(); // Create a client for the above `Environment` with an ID let id = "alice"; @@ -34,12 +34,12 @@ fn main() { These created clients can then get access to making calls and transactions to contracts deployed into the `Environment`'s world state. We can do the following: ```rust use arbiter_core::middleware::RevmMiddleware; -use arbiter_core::environment::EnvironmentBuilder; +use arbiter_core::environment::Environment; use arbiter_bindings::bindings::arbiter_token::ArbiterToken; #[tokio::main] async fn main() { - let env = EnvironmentBuilder::new().build(); + let env = Environment::builder().build(); let client = RevmMiddleware::new(&env, None).unwrap(); // Deploy a contract From cd88b06b391d23cb7f15d82f7c7765c04650f163 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 1 Feb 2024 11:56:22 -0500 Subject: [PATCH 11/38] fix:tests --- arbiter-core/src/coprocessor.rs | 1 - arbiter-core/src/middleware/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/arbiter-core/src/coprocessor.rs b/arbiter-core/src/coprocessor.rs index 7a35d3166..6a57b4727 100644 --- a/arbiter-core/src/coprocessor.rs +++ b/arbiter-core/src/coprocessor.rs @@ -49,7 +49,6 @@ mod tests { use revm_primitives::{InvalidTransaction, U256}; use super::*; - use crate::environment::EnvironmentBuilder; #[test] fn coprocessor() { diff --git a/arbiter-core/src/middleware/mod.rs b/arbiter-core/src/middleware/mod.rs index 63774257b..531b94998 100644 --- a/arbiter-core/src/middleware/mod.rs +++ b/arbiter-core/src/middleware/mod.rs @@ -83,7 +83,7 @@ pub mod nonce_middleware; /// // Import `Arc` if you need to create a client instance /// use std::sync::Arc; /// -/// use arbiter_core::{environment::EnvironmentBuilder, middleware::RevmMiddleware}; +/// use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; /// /// // Create a new environment and run it /// let mut environment = Environment::builder().build(); @@ -242,7 +242,7 @@ impl RevmMiddleware { /// // Import `Arc` if you need to create a client instance /// use std::sync::Arc; /// - /// use arbiter_core::{environment::EnvironmentBuilder, middleware::RevmMiddleware}; + /// use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; /// /// // Create a new environment and run it /// let mut environment = Environment::builder().build(); From 85e7b429f01b57c2710dc515f8355ab2ad802245 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 1 Feb 2024 12:01:04 -0500 Subject: [PATCH 12/38] fix: fmt --- arbiter-engine/src/agent.rs | 160 ++++++++++---------- arbiter-engine/src/examples/token_minter.rs | 82 +++++----- 2 files changed, 121 insertions(+), 121 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 083d4b687..349c38163 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -68,8 +68,8 @@ use crate::{ /// 5. [`State::Stopped`]: The [`Agent`] is stopped. This is where the [`Agent`] /// can be stopped and state of the [`World`] and its [`Agent`]s can be /// offloaded and saved. -// todo(matt): use builder pattern where we just have the agent builder implement deserialize with -// just behavior_engines +// todo(matt): use builder pattern where we just have the agent builder +// implement deserialize with just behavior_engines // // #[derive(Serialize, Deserialize)] // pub struct AgentBuilder { @@ -284,85 +284,87 @@ mod tests { let world = World::new("world"); let agent = Agent::builder("agent").unwrap(); - /* - - let arb = ArbiterToken::deploy( - agent.client.clone(), - ("ArbiterToken".to_string(), "ARB".to_string(), 18u8), - ) - .unwrap() - .send() - .await - .unwrap(); - - let mut agent = agent.with_event(arb.events()); - let address = agent.client.address(); - + // let arb = ArbiterToken::deploy( + // agent.client.clone(), + // ("ArbiterToken".to_string(), "ARB".to_string(), 18u8), + // ) + // .unwrap() + // .send() + // .await + // .unwrap(); + // + // let mut agent = agent.with_event(arb.events()); + // let address = agent.client.address(); + // // TODO: (START BLOCK) It would be nice to get this block to be a single // function that isn't copy and pasted from above. - let messager = agent.messager.take().unwrap(); - let message_stream = messager - .stream() - .map(|msg| serde_json::to_string(&msg).unwrap_or_else(|e| e.to_string())); - let eth_event_stream = agent.event_streamer.take().unwrap().stream(); - - let mut event_stream: Pin + Send + '_>> = - if let Some(event_stream) = eth_event_stream { - trace!("Merging event streams."); - let all_streams = vec![ - Box::pin(message_stream) as Pin + Send>>, - Box::pin(event_stream), - ]; - Box::pin(futures::stream::select_all(all_streams)) - } else { - trace!("Agent only sees message stream."); - Box::pin(message_stream) - }; + // let messager = agent.messager.take().unwrap(); + // let message_stream = messager + // .stream() + // .map(|msg| serde_json::to_string(&msg).unwrap_or_else(|e| + // e.to_string())); let eth_event_stream = + // agent.event_streamer.take().unwrap().stream(); + // + // let mut event_stream: Pin + Send + '_>> + // = if let Some(event_stream) = eth_event_stream { + // trace!("Merging event streams."); + // let all_streams = vec![ + // Box::pin(message_stream) as Pin + + // Send>>, Box::pin(event_stream), + // ]; + // Box::pin(futures::stream::select_all(all_streams)) + // } else { + // trace!("Agent only sees message stream."); + // Box::pin(message_stream) + // }; // TODO: (END BLOCK) - - let outside_messager = world.messager.join_with_id(None); - let message_task = tokio::spawn(async move { - for _ in 0..5 { - outside_messager - .send(Message { - from: "god".to_string(), - to: messager::To::All, - data: "hello".to_string(), - }) - .await; - } - }); - - let eth_event_task = tokio::spawn(async move { - for i in 0..5 { - if i == 0 { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - arb.approve(address, U256::from(1)) - .send() - .await - .unwrap() - .await - .unwrap(); - } - }); - - let mut idx = 0; - let print_task = tokio::spawn(async move { - while let Some(msg) = event_stream.next().await { - println!("Printing message in test: {:?}", msg); - if idx < 5 { - assert_eq!(msg, "{\"from\":\"god\",\"to\":\"All\",\"data\":\"hello\"}"); - } else { - assert_eq!(msg, "{\"ApprovalFilter\":{\"owner\":\"0xe7a46f3d9f0e9b9c02f58f95e3bcee2db54050b0\",\"spender\":\"0xe7a46f3d9f0e9b9c02f58f95e3bcee2db54050b0\",\"amount\":\"0x1\"}}"); - } - idx += 1; - if idx == 10 { - break; - } - } - }); - join_all(vec![message_task, eth_event_task, print_task]).await; - */ + // + // let outside_messager = world.messager.join_with_id(None); + // let message_task = tokio::spawn(async move { + // for _ in 0..5 { + // outside_messager + // .send(Message { + // from: "god".to_string(), + // to: messager::To::All, + // data: "hello".to_string(), + // }) + // .await; + // } + // }); + // + // let eth_event_task = tokio::spawn(async move { + // for i in 0..5 { + // if i == 0 { + // tokio::time::sleep(std::time::Duration::from_secs(1)).await; + // } + // arb.approve(address, U256::from(1)) + // .send() + // .await + // .unwrap() + // .await + // .unwrap(); + // } + // }); + // + // let mut idx = 0; + // let print_task = tokio::spawn(async move { + // while let Some(msg) = event_stream.next().await { + // println!("Printing message in test: {:?}", msg); + // if idx < 5 { + // assert_eq!(msg, + // "{\"from\":\"god\",\"to\":\"All\",\"data\":\"hello\"}"); + // } else { + // assert_eq!(msg, + // "{\"ApprovalFilter\":{\"owner\":\" + // 0xe7a46f3d9f0e9b9c02f58f95e3bcee2db54050b0\",\"spender\":\" + // 0xe7a46f3d9f0e9b9c02f58f95e3bcee2db54050b0\",\"amount\":\"0x1\"}}"); + // } + // idx += 1; + // if idx == 10 { + // break; + // } + // } + // }); + // join_all(vec![message_task, eth_event_task, print_task]).await; } } diff --git a/arbiter-engine/src/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs index d6d064f06..c7dc8adc7 100644 --- a/arbiter-engine/src/examples/token_minter.rs +++ b/arbiter-engine/src/examples/token_minter.rs @@ -259,8 +259,8 @@ token: {:?}", Some(MachineHalt) } } -// world.run() -> sync state -> iterate over agents, `execute` each agent -> agent sync state -> -// iterate over behaviors -> behavior sync state +// world.run() -> sync state -> iterate over agents, `execute` each agent -> +// agent sync state -> iterate over behaviors -> behavior sync state #[async_trait::async_trait] impl Behavior for TokenRequester { async fn sync(&mut self, messager: Messager, client: Arc) { @@ -301,7 +301,7 @@ impl Behavior for TokenRequester { async fn token_minter_simulation() { // 3. have a method on world to update mutable map of addresses let mut world = World::new("test_world"); - //self.contracts: HashMap + // self.contracts: HashMap // Create the token admin agent // 1. use agent builder struct to get rid of reference to world @@ -319,43 +319,41 @@ async fn token_minter_simulation() { let token_requester = Agent::builder(REQUESTER_ID).unwrap(); let token_requester_behavior = TokenRequester::new(0, Some(4)); // 2. appropriately handle event driven behaviors - /* - let arb = ArbiterToken::new( - Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), - token_requester.client.clone(), - ); - let transfer_event = arb.transfer_filter(); - - let token_requester_behavior_again = TokenRequester::new(0, Some(4)); - world.add_agent( - token_requester - .with_behavior::(token_requester_behavior) - .with_behavior::(token_requester_behavior_again) - .with_event(transfer_event), - ); - - let transfer_stream = EventLogger::builder() - .add_stream(arb.transfer_filter()) - .stream() - .unwrap(); - let mut stream = Box::pin(transfer_stream); - let mut idx = 0; - - world.run().await; - - loop { - match timeout(Duration::from_secs(1), stream.next()).await { - Ok(Some(event)) => { - println!("Event received in outside world: {:?}", event); - idx += 1; - if idx == 4 { - break; - } - } - _ => { - panic!("Timeout reached. Test failed."); - } - } - } - */ + // let arb = ArbiterToken::new( + // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), + // token_requester.client.clone(), + // ); + // let transfer_event = arb.transfer_filter(); + // + // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); + // world.add_agent( + // token_requester + // .with_behavior::(token_requester_behavior) + // .with_behavior::(token_requester_behavior_again) + // .with_event(transfer_event), + // ); + // + // let transfer_stream = EventLogger::builder() + // .add_stream(arb.transfer_filter()) + // .stream() + // .unwrap(); + // let mut stream = Box::pin(transfer_stream); + // let mut idx = 0; + // + // world.run().await; + // + // loop { + // match timeout(Duration::from_secs(1), stream.next()).await { + // Ok(Some(event)) => { + // println!("Event received in outside world: {:?}", event); + // idx += 1; + // if idx == 4 { + // break; + // } + // } + // _ => { + // panic!("Timeout reached. Test failed."); + // } + // } + // } } From a9b0d98f165dd1b37cebbfff1a72154e3b3e2ddd Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 1 Feb 2024 12:18:45 -0500 Subject: [PATCH 13/38] wip --- arbiter-engine/src/machine.rs | 41 ++++------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 856f42c97..8897f5ed0 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -23,21 +23,14 @@ use super::*; /// The instructions that can be sent to a [`StateMachine`]. #[derive(Clone, Debug)] pub enum MachineInstruction { - /// Used to make a [`StateMachine`] sync with the world. - Sync(Option, Option>), - /// Used to make a [`StateMachine`] start up. - Start, + Start(Option>, Option), /// Used to make a [`StateMachine`] process events. /// This will offload the process into a task that can be halted by sending /// a [`MachineHalt`] message from the [`Messager`]. For our purposes, the /// [`crate::world::World`] will handle this. Process, - - /// Used to make a [`StateMachine`] stop. Only applicable for the - /// [`crate::world::World`] currently. - Stop, } /// The message that can be used in a [`StateMachine`] to halt its processing. @@ -52,12 +45,6 @@ pub enum State { /// This is the state adopted by the entity when it is first created. Uninitialized, - /// The entity is syncing with the world. - /// This can be used to bring the entity back up to date with the latest - /// state of the world. This could be used if the world was stopped and - /// later restarted. - Syncing, - /// The entity is starting up. /// This is where the entity can engage in its specific start up activities /// that it can do given the current state of the world. @@ -68,10 +55,6 @@ pub enum State { /// This is where the entity can engage in its specific processing /// of events that can lead to actions being taken. Processing, - - /// The entity is stopped. - /// This is where state can be offloaded and saved if need be. - Stopped, } // NOTE: `async_trait::async_trait` is used throughout to make the trait object @@ -81,14 +64,10 @@ pub enum State { /// by a [`StateMachine`]. This constitutes what each state transition will do. #[async_trait::async_trait] pub trait Behavior: Send + Sync + 'static { - /// Used to bring the agent back up to date with the latest state of the - /// world. This could be used if the world was stopped and later restarted. - async fn sync(&mut self, _messager: Messager, _client: Arc) {} - /// Used to start the agent. /// This is where the agent can engage in its specific start up activities /// that it can do given the current state of the world. - async fn startup(&mut self) {} + async fn startup(&mut self, client: Arc, messager: Messager) {} /// Used to process events. /// This is where the agent can engage in its specific processing @@ -151,22 +130,13 @@ where { async fn execute(&mut self, instruction: MachineInstruction) { match instruction { - MachineInstruction::Sync(messager, client) => { - trace!("Behavior is syncing."); - self.state = State::Syncing; + MachineInstruction::Start(client, messager) => { let mut behavior = self.behavior.take().unwrap(); - let behavior_task = tokio::spawn(async move { - behavior.sync(messager.unwrap(), client.unwrap()).await; - behavior - }); - self.behavior = Some(behavior_task.await.unwrap()); - } - MachineInstruction::Start => { trace!("Behavior is starting up."); self.state = State::Starting; let mut behavior = self.behavior.take().unwrap(); let behavior_task = tokio::spawn(async move { - behavior.startup().await; + behavior.startup(client.unwrap(), messager.unwrap()).await; behavior }); self.behavior = Some(behavior_task.await.unwrap()); @@ -203,9 +173,6 @@ where }); self.behavior = Some(behavior_task.await.unwrap()); } - MachineInstruction::Stop => { - unreachable!("This is never explicitly called on an engine.") - } } } } From 409f3748984d90971ebb5e1704c93beeaaeb154e Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 1 Feb 2024 10:39:35 -0700 Subject: [PATCH 14/38] refactor: machine + agent --- Cargo.lock | 134 ++-- arbiter-engine/src/agent.rs | 16 +- arbiter-engine/src/examples/timed_message.rs | 19 +- arbiter-engine/src/examples/token_minter.rs | 720 ++++++++++--------- arbiter-engine/src/machine.rs | 55 +- arbiter-engine/src/world.rs | 39 +- 6 files changed, 461 insertions(+), 522 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4a0d37ff..f0e7d9609 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" [[package]] name = "anstyle-parse" @@ -586,14 +586,13 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "823b8bb275161044e2ac7a25879cb3e2480cb403e3943022c7c769c599b756aa" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.43", ] [[package]] @@ -772,7 +771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "serde", ] @@ -802,9 +801,9 @@ checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" dependencies = [ "bytemuck_derive", ] @@ -1892,9 +1891,9 @@ checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" [[package]] name = "eyre" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -2223,7 +2222,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -2514,9 +2513,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -2772,9 +2771,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" @@ -3056,6 +3055,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -3470,18 +3475,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", @@ -3976,30 +3981,6 @@ dependencies = [ "toml_edit 0.21.1", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.78" @@ -4044,11 +4025,11 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "memchr", "unicase", ] @@ -4191,13 +4172,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -4212,9 +4193,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -4241,9 +4222,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64 0.21.7", "bytes", @@ -4267,6 +4248,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -4514,9 +4496,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags 2.4.2", "errno", @@ -4675,9 +4657,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f622567e3b4b38154fb8190bcf6b160d7a4301d70595a49195b48c116007a27" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ "secp256k1-sys", ] @@ -5134,9 +5116,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "svm-rs" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20689c7d03b6461b502d0b95d6c24874c7d24dea2688af80486a130a06af3b07" +checksum = "11297baafe5fa0c99d5722458eac6a5e25c01eb1b8e5cd137f54079093daa7a4" dependencies = [ "dirs", "fs2", @@ -5187,6 +5169,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sysinfo" version = "0.30.5" @@ -5326,12 +5314,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "fe80ced77cbfb4cb91a94bf72b378b4b6791a0d9b7f09d0be747d1bdff4e68bd" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -5346,10 +5335,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -5695,9 +5685,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "uncased" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" dependencies = [ "version_check", ] @@ -5797,9 +5787,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-trait" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea87257cfcbedcb9444eda79c59fdfea71217e6305afee8ee33f500375c2ac97" +checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" dependencies = [ "float-cmp", "halfbrown", @@ -5943,9 +5933,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68938b57b33da363195412cfc5fc37c9ed49aa9cfe2156fde64b8d2c9498242" +checksum = "89beec544f246e679fc25490e3f8e08003bc4bf612068f325120dad4cea02c1c" dependencies = [ "bytemuck", "safe_arch", @@ -6135,9 +6125,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.34" +version = "0.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +checksum = "818ce546a11a9986bc24f93d0cdf38a8a1a400f1473ea8c82e59f6e0ffab9249" dependencies = [ "memchr", ] diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 349c38163..038ec8eb5 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -215,19 +215,15 @@ impl StateMachine for Agent { #[tracing::instrument(skip(self), fields(id = self.id))] async fn execute(&mut self, instruction: MachineInstruction) { match instruction { - MachineInstruction::Sync(_, _) => { + MachineInstruction::Start(_, _) => { debug!("Agent is syncing."); - self.state = State::Syncing; - self.run(MachineInstruction::Sync( - self.messager.clone(), + self.state = State::Starting; + self.run(MachineInstruction::Start( Some(self.client.clone()), + self.messager.clone(), )) .await; } - MachineInstruction::Start => { - debug!("Agent is starting up."); - self.run(instruction).await; - } MachineInstruction::Process => { debug!("Agent is processing."); self.state = State::Processing; @@ -262,9 +258,6 @@ impl StateMachine for Agent { })); self.run(instruction).await; } - MachineInstruction::Stop => { - unreachable!("This is never explicitly called on an agent.") - } } } } @@ -366,5 +359,6 @@ mod tests { // } // }); // join_all(vec![message_task, eth_event_task, print_task]).await; + panic!() } } diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 925b5fcce..65406cdb3 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -2,8 +2,10 @@ const AGENT_ID: &str = "agent"; -use std::time::Duration; +use std::{pin::Pin, time::Duration}; +use ethers::types::BigEndianHash; +use futures_util::Stream; use tokio::time::timeout; use self::machine::MachineHalt; @@ -67,17 +69,16 @@ impl Behavior for TimedMessage { None } - async fn sync(&mut self, messager: Messager, _client: Arc) { + async fn startup( + &mut self, + _client: Arc, + messager: Messager, + ) -> Pin + Send + Sync>> { trace!("Syncing state for `TimedMessage`."); - self.messager = Some(messager); + self.messager = Some(messager.clone()); tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; trace!("Synced state for `TimedMessage`."); - } - - async fn startup(&mut self) { - trace!("Starting up `TimedMessage`."); - tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; - trace!("Started up `TimedMessage`."); + return Box::pin(messager.stream()); } } diff --git a/arbiter-engine/src/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs index c7dc8adc7..7420fd7f0 100644 --- a/arbiter-engine/src/examples/token_minter.rs +++ b/arbiter-engine/src/examples/token_minter.rs @@ -1,359 +1,361 @@ -use std::{str::FromStr, time::Duration}; - -use anyhow::Context; -use arbiter_bindings::bindings::arbiter_token; -use arbiter_core::data_collection::EventLogger; -use ethers::{ - abi::token, - types::{transaction::request, Filter}, -}; -use tokio::time::timeout; -use tracing::error; - -use self::machine::MachineHalt; -use super::*; -use crate::{ - agent::Agent, - machine::{Behavior, MachineInstruction, StateMachine}, - messager::To, - world::World, -}; - -const TOKEN_ADMIN_ID: &str = "token_admin"; -const REQUESTER_ID: &str = "requester"; -const TOKEN_NAME: &str = "Arbiter Token"; -const TOKEN_SYMBOL: &str = "ARB"; -const TOKEN_DECIMALS: u8 = 18; - -/// The token admin is responsible for handling token minting requests. -#[derive(Debug)] -pub struct TokenAdmin { - /// The identifier of the token admin. - pub token_data: HashMap, - - pub tokens: Option>>, - - pub client: Option>, - - pub messager: Option, - - count: u64, - - max_count: Option, -} - -impl TokenAdmin { - pub fn new(count: u64, max_count: Option) -> Self { - Self { - token_data: HashMap::new(), - tokens: None, - client: None, - messager: None, - count, - max_count, - } - } - - /// Adds a token to the token admin. - pub fn add_token(&mut self, token_data: TokenData) { - self.token_data.insert(token_data.name.clone(), token_data); - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct TokenData { - pub name: String, - pub symbol: String, - pub decimals: u8, - pub address: Option
, -} - -/// Used as an action to ask what tokens are available. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum TokenAdminQuery { - /// Get the address of the token. - AddressOf(String), - - /// Mint tokens. - MintRequest(MintRequest), -} - -/// Used as an action to mint tokens. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct MintRequest { - /// The token to mint. - pub token: String, - - /// The address to mint to. - pub mint_to: Address, - - /// The amount to mint. - pub mint_amount: u64, -} - -#[async_trait::async_trait] -impl Behavior for TokenAdmin { - #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] - async fn sync(&mut self, messager: Messager, client: Arc) { - for token_data in self.token_data.values_mut() { - let token = ArbiterToken::deploy( - client.clone(), - ( - token_data.name.clone(), - token_data.symbol.clone(), - token_data.decimals, - ), - ) - .unwrap() - .send() - .await - .unwrap(); - token_data.address = Some(token.address()); - self.tokens - .get_or_insert_with(HashMap::new) - .insert(token_data.name.clone(), token.clone()); - debug!("Deployed token: {:?}", token); - } - self.messager = Some(messager); - self.client = Some(client); - } - - #[tracing::instrument(skip(self), fields(id = self.messager.as_ref().unwrap().id.as_deref()))] - async fn process(&mut self, event: Message) -> Option { - if self.tokens.is_none() { - error!( - "There were no tokens to deploy! You must add tokens to -the token admin before running the simulation." - ); - } - - let query: TokenAdminQuery = serde_json::from_str(&event.data).unwrap(); - trace!("Got query: {:?}", query); - let messager = self.messager.as_ref().unwrap(); - match query { - TokenAdminQuery::AddressOf(token_name) => { - trace!( - "Getting address of token with name: {:?}", - token_name.clone() - ); - let token_data = self.token_data.get(&token_name).unwrap(); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(event.from.clone()), // Reply back to sender - data: serde_json::to_string(token_data).unwrap(), - }; - messager.send(message).await; - } - TokenAdminQuery::MintRequest(mint_request) => { - trace!("Minting tokens: {:?}", mint_request); - let token = self - .tokens - .as_ref() - .unwrap() - .get(&mint_request.token) - .unwrap(); - token - .mint(mint_request.mint_to, U256::from(mint_request.mint_amount)) - .send() - .await - .unwrap() - .await - .unwrap(); - self.count += 1; - if self.count == self.max_count.unwrap_or(u64::MAX) { - warn!("Reached max count. Halting behavior."); - return Some(MachineHalt); - } - } - } - None - } -} - -/// The token requester is responsible for requesting tokens from the token -/// admin. This agents is purely for testing purposes as far as I can tell. -#[derive(Debug)] -pub struct TokenRequester { - /// The tokens that the token requester has requested. - pub token_data: TokenData, - - /// The agent ID to request tokens to. - pub request_to: String, - - /// Client to have an address to receive token mint to and check balance - pub client: Option>, - - /// The messaging layer for the token requester. - pub messager: Option, - - pub count: u64, - - pub max_count: Option, -} - -impl TokenRequester { - pub fn new(count: u64, max_count: Option) -> Self { - Self { - token_data: TokenData { - name: TOKEN_NAME.to_owned(), - symbol: TOKEN_SYMBOL.to_owned(), - decimals: TOKEN_DECIMALS, - address: None, - }, - request_to: TOKEN_ADMIN_ID.to_owned(), - client: None, - messager: None, - count, - max_count, - } - } -} - -#[async_trait::async_trait] -impl Behavior for TokenRequester { - #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] - async fn sync(&mut self, messager: Messager, client: Arc) { - self.messager = Some(messager); - self.client = Some(client); - } - - #[tracing::instrument(skip(self), fields(id = self.messager.as_ref().unwrap().id.as_deref()))] - async fn startup(&mut self) { - let messager = self.messager.as_ref().unwrap(); - trace!("Requesting address of token: {:?}", self.token_data.name); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name.clone())) - .unwrap(), - }; - messager.send(message).await; - } - - #[tracing::instrument(skip(self), fields(id = self.messager.as_ref().unwrap().id.as_deref()))] - async fn process(&mut self, event: Message) -> Option { - if let Ok(token_data) = serde_json::from_str::(&event.data) { - let messager = self.messager.as_ref().unwrap(); - trace!( - "Got -token data: {:?}", - token_data - ); - trace!( - "Requesting first mint of -token: {:?}", - self.token_data.name - ); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { - token: self.token_data.name.clone(), - mint_to: self.client.as_ref().unwrap().address(), - mint_amount: 1, - })) - .unwrap(), - }; - messager.send(message).await; - } - Some(MachineHalt) - } -} -// world.run() -> sync state -> iterate over agents, `execute` each agent -> -// agent sync state -> iterate over behaviors -> behavior sync state -#[async_trait::async_trait] -impl Behavior for TokenRequester { - async fn sync(&mut self, messager: Messager, client: Arc) { - self.client = Some(client); - self.messager = Some(messager); - } - - #[tracing::instrument(skip(self), fields(id = self.messager.as_ref().unwrap().id.as_deref()))] - async fn process(&mut self, event: arbiter_token::TransferFilter) -> Option { - let messager = self.messager.as_ref().unwrap(); - trace!( - "Got event for -`TokenRequester` logger: {:?}", - event - ); - std::thread::sleep(std::time::Duration::from_secs(1)); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { - token: self.token_data.name.clone(), - mint_to: self.client.as_ref().unwrap().address(), - mint_amount: 1, - })) - .unwrap(), - }; - messager.send(message).await; - self.count += 1; - if self.count == self.max_count.unwrap_or(u64::MAX) { - warn!("Reached max count. Halting behavior."); - return Some(MachineHalt); - } - None - } -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn token_minter_simulation() { - // 3. have a method on world to update mutable map of addresses - let mut world = World::new("test_world"); - // self.contracts: HashMap - - // Create the token admin agent - // 1. use agent builder struct to get rid of reference to world - let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); - let mut token_admin_behavior = TokenAdmin::new(0, Some(4)); - token_admin_behavior.add_token(TokenData { - name: TOKEN_NAME.to_owned(), - symbol: TOKEN_SYMBOL.to_owned(), - decimals: TOKEN_DECIMALS, - address: None, - }); - world.add_agent(token_admin.with_behavior(token_admin_behavior)); - - // Create the token requester agent - let token_requester = Agent::builder(REQUESTER_ID).unwrap(); - let token_requester_behavior = TokenRequester::new(0, Some(4)); - // 2. appropriately handle event driven behaviors - // let arb = ArbiterToken::new( - // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), - // token_requester.client.clone(), - // ); - // let transfer_event = arb.transfer_filter(); - // - // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); - // world.add_agent( - // token_requester - // .with_behavior::(token_requester_behavior) - // .with_behavior::(token_requester_behavior_again) - // .with_event(transfer_event), - // ); - // - // let transfer_stream = EventLogger::builder() - // .add_stream(arb.transfer_filter()) - // .stream() - // .unwrap(); - // let mut stream = Box::pin(transfer_stream); - // let mut idx = 0; - // - // world.run().await; - // - // loop { - // match timeout(Duration::from_secs(1), stream.next()).await { - // Ok(Some(event)) => { - // println!("Event received in outside world: {:?}", event); - // idx += 1; - // if idx == 4 { - // break; - // } - // } - // _ => { - // panic!("Timeout reached. Test failed."); - // } - // } - // } -} +// use std::{str::FromStr, time::Duration}; + +// use anyhow::Context; +// use arbiter_bindings::bindings::arbiter_token; +// use arbiter_core::data_collection::EventLogger; +// use ethers::{ +// abi::token, +// types::{transaction::request, Filter}, +// }; +// use tokio::time::timeout; +// use tracing::error; + +// use self::machine::MachineHalt; +// use super::*; +// use crate::{ +// agent::Agent, +// machine::{Behavior, MachineInstruction, StateMachine}, +// messager::To, +// world::World, +// }; + +// const TOKEN_ADMIN_ID: &str = "token_admin"; +// const REQUESTER_ID: &str = "requester"; +// const TOKEN_NAME: &str = "Arbiter Token"; +// const TOKEN_SYMBOL: &str = "ARB"; +// const TOKEN_DECIMALS: u8 = 18; + +// /// The token admin is responsible for handling token minting requests. +// #[derive(Debug)] +// pub struct TokenAdmin { +// /// The identifier of the token admin. +// pub token_data: HashMap, + +// pub tokens: Option>>, + +// pub client: Option>, + +// pub messager: Option, + +// count: u64, + +// max_count: Option, +// } + +// impl TokenAdmin { +// pub fn new(count: u64, max_count: Option) -> Self { +// Self { +// token_data: HashMap::new(), +// tokens: None, +// client: None, +// messager: None, +// count, +// max_count, +// } +// } + +// /// Adds a token to the token admin. +// pub fn add_token(&mut self, token_data: TokenData) { +// self.token_data.insert(token_data.name.clone(), token_data); +// } +// } + +// #[derive(Clone, Debug, Deserialize, Serialize)] +// pub struct TokenData { +// pub name: String, +// pub symbol: String, +// pub decimals: u8, +// pub address: Option
, +// } + +// /// Used as an action to ask what tokens are available. +// #[derive(Clone, Debug, Deserialize, Serialize)] +// pub enum TokenAdminQuery { +// /// Get the address of the token. +// AddressOf(String), + +// /// Mint tokens. +// MintRequest(MintRequest), +// } + +// /// Used as an action to mint tokens. +// #[derive(Clone, Debug, Deserialize, Serialize)] +// pub struct MintRequest { +// /// The token to mint. +// pub token: String, + +// /// The address to mint to. +// pub mint_to: Address, + +// /// The amount to mint. +// pub mint_amount: u64, +// } + +// #[async_trait::async_trait] +// impl Behavior for TokenAdmin { +// #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] +// async fn sync(&mut self, messager: Messager, client: Arc) +// { for token_data in self.token_data.values_mut() { +// let token = ArbiterToken::deploy( +// client.clone(), +// ( +// token_data.name.clone(), +// token_data.symbol.clone(), +// token_data.decimals, +// ), +// ) +// .unwrap() +// .send() +// .await +// .unwrap(); +// token_data.address = Some(token.address()); +// self.tokens +// .get_or_insert_with(HashMap::new) +// .insert(token_data.name.clone(), token.clone()); +// debug!("Deployed token: {:?}", token); +// } +// self.messager = Some(messager); +// self.client = Some(client); +// } + +// #[tracing::instrument(skip(self), fields(id = +// self.messager.as_ref().unwrap().id.as_deref()))] async fn process(&mut +// self, event: Message) -> Option { if +// self.tokens.is_none() { error!( +// "There were no tokens to deploy! You must add tokens to +// the token admin before running the simulation." +// ); +// } + +// let query: TokenAdminQuery = +// serde_json::from_str(&event.data).unwrap(); trace!("Got query: {:?}", +// query); let messager = self.messager.as_ref().unwrap(); +// match query { +// TokenAdminQuery::AddressOf(token_name) => { +// trace!( +// "Getting address of token with name: {:?}", +// token_name.clone() +// ); +// let token_data = self.token_data.get(&token_name).unwrap(); +// let message = Message { +// from: messager.id.clone().unwrap(), +// to: To::Agent(event.from.clone()), // Reply back to +// sender data: serde_json::to_string(token_data).unwrap(), +// }; +// messager.send(message).await; +// } +// TokenAdminQuery::MintRequest(mint_request) => { +// trace!("Minting tokens: {:?}", mint_request); +// let token = self +// .tokens +// .as_ref() +// .unwrap() +// .get(&mint_request.token) +// .unwrap(); +// token +// .mint(mint_request.mint_to, +// U256::from(mint_request.mint_amount)) .send() +// .await +// .unwrap() +// .await +// .unwrap(); +// self.count += 1; +// if self.count == self.max_count.unwrap_or(u64::MAX) { +// warn!("Reached max count. Halting behavior."); +// return Some(MachineHalt); +// } +// } +// } +// None +// } +// } + +// /// The token requester is responsible for requesting tokens from the token +// /// admin. This agents is purely for testing purposes as far as I can tell. +// #[derive(Debug)] +// pub struct TokenRequester { +// /// The tokens that the token requester has requested. +// pub token_data: TokenData, + +// /// The agent ID to request tokens to. +// pub request_to: String, + +// /// Client to have an address to receive token mint to and check balance +// pub client: Option>, + +// /// The messaging layer for the token requester. +// pub messager: Option, + +// pub count: u64, + +// pub max_count: Option, +// } + +// impl TokenRequester { +// pub fn new(count: u64, max_count: Option) -> Self { +// Self { +// token_data: TokenData { +// name: TOKEN_NAME.to_owned(), +// symbol: TOKEN_SYMBOL.to_owned(), +// decimals: TOKEN_DECIMALS, +// address: None, +// }, +// request_to: TOKEN_ADMIN_ID.to_owned(), +// client: None, +// messager: None, +// count, +// max_count, +// } +// } +// } + +// #[async_trait::async_trait] +// impl Behavior for TokenRequester { +// #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] +// async fn sync(&mut self, messager: Messager, client: Arc) +// { self.messager = Some(messager); +// self.client = Some(client); +// } + +// #[tracing::instrument(skip(self), fields(id = +// self.messager.as_ref().unwrap().id.as_deref()))] async fn startup(&mut +// self) { let messager = self.messager.as_ref().unwrap(); +// trace!("Requesting address of token: {:?}", self.token_data.name); +// let message = Message { +// from: messager.id.clone().unwrap(), +// to: To::Agent(self.request_to.clone()), +// data: +// serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name. +// clone())) .unwrap(), +// }; +// messager.send(message).await; +// } + +// #[tracing::instrument(skip(self), fields(id = +// self.messager.as_ref().unwrap().id.as_deref()))] async fn process(&mut +// self, event: Message) -> Option { if let Ok(token_data) +// = serde_json::from_str::(&event.data) { let messager = +// self.messager.as_ref().unwrap(); trace!( +// "Got +// token data: {:?}", +// token_data +// ); +// trace!( +// "Requesting first mint of +// token: {:?}", +// self.token_data.name +// ); +// let message = Message { +// from: messager.id.clone().unwrap(), +// to: To::Agent(self.request_to.clone()), +// data: +// serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { +// token: self.token_data.name.clone(), mint_to: +// self.client.as_ref().unwrap().address(), mint_amount: 1, +// })) +// .unwrap(), +// }; +// messager.send(message).await; +// } +// Some(MachineHalt) +// } +// } +// // world.run() -> sync state -> iterate over agents, `execute` each agent -> +// // agent sync state -> iterate over behaviors -> behavior sync state +// #[async_trait::async_trait] +// impl Behavior for TokenRequester { +// async fn sync(&mut self, messager: Messager, client: Arc) +// { self.client = Some(client); +// self.messager = Some(messager); +// } + +// #[tracing::instrument(skip(self), fields(id = +// self.messager.as_ref().unwrap().id.as_deref()))] async fn process(&mut +// self, event: arbiter_token::TransferFilter) -> Option { +// let messager = self.messager.as_ref().unwrap(); +// trace!( +// "Got event for +// `TokenRequester` logger: {:?}", +// event +// ); +// std::thread::sleep(std::time::Duration::from_secs(1)); +// let message = Message { +// from: messager.id.clone().unwrap(), +// to: To::Agent(self.request_to.clone()), +// data: +// serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { +// token: self.token_data.name.clone(), mint_to: +// self.client.as_ref().unwrap().address(), mint_amount: 1, +// })) +// .unwrap(), +// }; +// messager.send(message).await; +// self.count += 1; +// if self.count == self.max_count.unwrap_or(u64::MAX) { +// warn!("Reached max count. Halting behavior."); +// return Some(MachineHalt); +// } +// None +// } +// } + +// #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +// async fn token_minter_simulation() { +// // 3. have a method on world to update mutable map of addresses +// let mut world = World::new("test_world"); +// // self.contracts: HashMap + +// // Create the token admin agent +// // 1. use agent builder struct to get rid of reference to world +// let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); +// let mut token_admin_behavior = TokenAdmin::new(0, Some(4)); +// token_admin_behavior.add_token(TokenData { +// name: TOKEN_NAME.to_owned(), +// symbol: TOKEN_SYMBOL.to_owned(), +// decimals: TOKEN_DECIMALS, +// address: None, +// }); +// world.add_agent(token_admin.with_behavior(token_admin_behavior)); + +// // Create the token requester agent +// let token_requester = Agent::builder(REQUESTER_ID).unwrap(); +// let token_requester_behavior = TokenRequester::new(0, Some(4)); +// // 2. appropriately handle event driven behaviors +// // let arb = ArbiterToken::new( +// // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f"). +// unwrap(), // token_requester.client.clone(), +// // ); +// // let transfer_event = arb.transfer_filter(); +// // +// // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); +// // world.add_agent( +// // token_requester +// // .with_behavior::(token_requester_behavior) +// // .with_behavior::(token_requester_behavior_again) +// // .with_event(transfer_event), +// // ); +// // +// // let transfer_stream = EventLogger::builder() +// // .add_stream(arb.transfer_filter()) +// // .stream() +// // .unwrap(); +// // let mut stream = Box::pin(transfer_stream); +// // let mut idx = 0; +// // +// // world.run().await; +// // +// // loop { +// // match timeout(Duration::from_secs(1), stream.next()).await { +// // Ok(Some(event)) => { +// // println!("Event received in outside world: {:?}", event); +// // idx += 1; +// // if idx == 4 { +// // break; +// // } +// // } +// // _ => { +// // panic!("Timeout reached. Test failed."); +// // } +// // } +// // } +// } diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 8897f5ed0..b3d442e21 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -2,18 +2,15 @@ //! [`Behavior`]s. // TODO: Notes -// I think we should have the `sync` stage of the behavior receive the client -// and messager and then the user can decide if it wants to use those in their -// behavior. - // Could typestate pattern help here at all? Sync could produce a `Synced` state // behavior that can then not have options for client and messager. Then the // user can decide if they want to use those in their behavior and get a bit // simpler UX. -use std::{fmt::Debug, sync::Arc}; +use std::{fmt::Debug, pin::Pin, sync::Arc}; use arbiter_core::middleware::RevmMiddleware; +use futures_util::{Stream, StreamExt}; use serde::de::DeserializeOwned; use tokio::sync::broadcast::Receiver; @@ -67,7 +64,11 @@ pub trait Behavior: Send + Sync + 'static { /// Used to start the agent. /// This is where the agent can engage in its specific start up activities /// that it can do given the current state of the world. - async fn startup(&mut self, client: Arc, messager: Messager) {} + async fn startup( + &mut self, + client: Arc, + messager: Messager, + ) -> Pin + Send + Sync>>; /// Used to process events. /// This is where the agent can engage in its specific processing @@ -101,7 +102,7 @@ where /// The receiver of events that the [`Engine`] will process. /// The [`State::Processing`] stage will attempt a decode of the [`String`]s /// into the event type ``. - event_receiver: Option>, + event_stream: Option + Send + Sync>>>, phantom: std::marker::PhantomData, } @@ -116,7 +117,7 @@ where Self { behavior: Some(behavior), state: State::Uninitialized, - event_receiver: Some(event_receiver), + event_stream: None, phantom: std::marker::PhantomData, } } @@ -131,46 +132,32 @@ where async fn execute(&mut self, instruction: MachineInstruction) { match instruction { MachineInstruction::Start(client, messager) => { - let mut behavior = self.behavior.take().unwrap(); trace!("Behavior is starting up."); self.state = State::Starting; let mut behavior = self.behavior.take().unwrap(); let behavior_task = tokio::spawn(async move { - behavior.startup(client.unwrap(), messager.unwrap()).await; - behavior + let stream = behavior.startup(client.unwrap(), messager.unwrap()).await; + (stream, behavior) }); - self.behavior = Some(behavior_task.await.unwrap()); + let (stream, behavior) = behavior_task.await.unwrap(); + self.event_stream = Some(stream); + self.behavior = Some(behavior); } MachineInstruction::Process => { trace!("Behavior is processing."); let mut behavior = self.behavior.take().unwrap(); - let mut receiver = self.event_receiver.take().unwrap(); + let mut stream = self.event_stream.take().unwrap(); let behavior_task = tokio::spawn(async move { - while let Ok(event) = receiver.recv().await { - let decoding_result = serde_json::from_str::(&event); - match decoding_result { - Ok(event) => { - let halt_option = behavior.process(event).await; - if halt_option.is_some() { - break; - } - } - Err(_) => match serde_json::from_str::(&event) { - Ok(_) => { - warn!("Behavior received `MachineHalt` message. Breaking!"); - break; - } - Err(_) => { - trace!( - "Event received by behavior that could not be deserialized." - ); - continue; - } - }, + while let Some(event) = stream.next().await { + let halt_option = behavior.process(event).await; + if halt_option.is_some() { + break; } } behavior }); + // TODO: This could be removed as we probably don't need to have the behavior + // stored once its done. self.behavior = Some(behavior_task.await.unwrap()); } } diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 548bb6325..fbba159c8 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -116,16 +116,9 @@ impl World { /// Runs the world through up to the [`State::Processing`] stage. pub async fn run(&mut self) { - self.execute(MachineInstruction::Sync(None, None)).await; - self.execute(MachineInstruction::Start).await; + self.execute(MachineInstruction::Start(None, None)).await; self.execute(MachineInstruction::Process).await; } - - /// Stops the world by stopping all the behaviors that each of the agents is - /// running. - pub async fn stop(&mut self) { - self.execute(MachineInstruction::Stop).await; - } } // TODO: Idea, when we enter the `State::Processing`, we should pass the task @@ -146,30 +139,8 @@ impl World { impl StateMachine for World { async fn execute(&mut self, instruction: MachineInstruction) { match instruction { - MachineInstruction::Sync(_, _) => { + MachineInstruction::Start(_, _) => { info!("World is syncing."); - self.state = State::Syncing; - let agents = self.agents.take().unwrap(); - let agent_tasks = join_all(agents.into_values().map(|mut agent| { - let instruction_clone = instruction.clone(); - tokio::spawn(async move { - agent.execute(instruction_clone).await; - agent - }) - })); - self.agents = Some( - agent_tasks - .await - .into_iter() - .map(|res| { - let agent = res.unwrap(); - (agent.id.clone(), agent) - }) - .collect::>(), - ); - } - MachineInstruction::Start => { - info!("World is starting up."); self.state = State::Starting; let agents = self.agents.take().unwrap(); let agent_tasks = join_all(agents.into_values().map(|mut agent| { @@ -215,12 +186,6 @@ impl StateMachine for World { .collect::>(), ); } - MachineInstruction::Stop => { - let halt = serde_json::to_string(&MachineHalt).unwrap(); - for tx in self.agent_distributors.take().unwrap() { - tx.send(halt.clone()).unwrap(); - } - } } } } From b55f84701897775cdcf5fba3217c9ad9c644dcce Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 1 Feb 2024 10:52:40 -0700 Subject: [PATCH 15/38] refactor: state machines --- arbiter-engine/src/agent.rs | 68 ++------------------ arbiter-engine/src/examples/timed_message.rs | 6 +- arbiter-engine/src/machine.rs | 4 +- arbiter-engine/src/world.rs | 34 +--------- 4 files changed, 14 insertions(+), 98 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 038ec8eb5..a744567d5 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -18,24 +18,17 @@ //! The agent module contains the core agent abstraction for the Arbiter Engine. -use std::{fmt::Debug, pin::Pin, sync::Arc}; +use std::{fmt::Debug, sync::Arc}; use arbiter_core::{data_collection::EventLogger, middleware::RevmMiddleware}; -use ethers::contract::{EthLogDecode, Event}; -use futures::stream::{Stream, StreamExt}; use futures_util::future::join_all; use serde::de::DeserializeOwned; -use tokio::{ - sync::broadcast::{channel, Receiver as BroadcastReceiver, Sender as BroadcastSender}, - task::JoinHandle, -}; use self::machine::MachineInstruction; use super::*; use crate::{ machine::{Behavior, Engine, State, StateMachine}, messager::Messager, - world::World, }; // TODO: For the time being, these agents are just meant to be for arbiter @@ -99,34 +92,17 @@ pub struct Agent { /// The engines/behaviors that the agent uses to sync, startup, and process /// events. behavior_engines: Vec>, - - /// The pipeline for yielding events from the centralized event streamer - /// (for both messages and Ethereum events) to agents. - pub(crate) distributor: (BroadcastSender, BroadcastReceiver), - - broadcast_task: Option + Send>>>>, } impl Agent { /// Produces a minimal agent builder with the given identifier. pub fn builder(id: &str) -> Result { - let distributor = channel(512); Ok(AgentBuilder { id: id.to_owned(), - distributor, behavior_engines: None, }) } - /// Adds an Ethereum event to the agent's event streamer. - pub fn with_event( - mut self, - event: Event, RevmMiddleware, D>, - ) -> Self { - self.event_streamer = Some(self.event_streamer.take().unwrap().add_stream(event)); - self - } - pub(crate) async fn run(&mut self, instruction: MachineInstruction) { let behavior_tasks = join_all(self.behavior_engines.drain(..).map(|mut engine| { let instruction_clone = instruction.clone(); @@ -167,9 +143,6 @@ pub struct AgentBuilder { /// The engines/behaviors that the agent uses to sync, startup, and process /// events. behavior_engines: Option>>, - /// The pipeline for yielding events from the centralized event streamer - /// (for both messages and Ethereum events) to agents. - pub(crate) distributor: (BroadcastSender, BroadcastReceiver), } impl AgentBuilder { @@ -177,9 +150,7 @@ impl AgentBuilder { mut self, behavior: impl Behavior + 'static, ) -> Self { - let event_receiver = self.distributor.0.subscribe(); - - let engine = Engine::new(behavior, event_receiver); + let engine = Engine::new(behavior); if let Some(engines) = &mut self.behavior_engines { engines.push(Box::new(engine)); } else { @@ -202,8 +173,6 @@ impl AgentBuilder { client, event_streamer: Some(EventLogger::builder()), behavior_engines: engines, - distributor: self.distributor, - broadcast_task: None, }), None => Err(AgentBuildError::MissingBehaviorEngines), } @@ -216,7 +185,7 @@ impl StateMachine for Agent { async fn execute(&mut self, instruction: MachineInstruction) { match instruction { MachineInstruction::Start(_, _) => { - debug!("Agent is syncing."); + debug!("Agent is starting."); self.state = State::Starting; self.run(MachineInstruction::Start( Some(self.client.clone()), @@ -227,35 +196,6 @@ impl StateMachine for Agent { MachineInstruction::Process => { debug!("Agent is processing."); self.state = State::Processing; - let messager = self.messager.take().unwrap(); - let message_stream = messager - .stream() - .map(|msg| serde_json::to_string(&msg).unwrap_or_else(|e| e.to_string())); - - let eth_event_stream = self.event_streamer.take().unwrap().stream(); - - let mut event_stream: Pin + Send + '_>> = - if let Some(event_stream) = eth_event_stream { - trace!("Merging event streams."); - // Convert the individual streams into a Vec - let all_streams = vec![ - Box::pin(message_stream) as Pin + Send>>, - Box::pin(event_stream), - ]; - // Use select_all to combine them - Box::pin(futures::stream::select_all(all_streams)) - } else { - trace!("Agent only sees message stream."); - Box::pin(message_stream) - }; - - let sender = self.distributor.0.clone(); - self.broadcast_task = Some(tokio::spawn(async move { - while let Some(event) = event_stream.next().await { - sender.send(event).unwrap(); - } - event_stream - })); self.run(instruction).await; } } @@ -268,7 +208,7 @@ mod tests { use ethers::types::U256; use super::*; - use crate::messager::Message; + use crate::{messager::Message, world::World}; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn streaming() { diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 65406cdb3..5cd368dec 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -74,16 +74,18 @@ impl Behavior for TimedMessage { _client: Arc, messager: Messager, ) -> Pin + Send + Sync>> { - trace!("Syncing state for `TimedMessage`."); + trace!("Starting up `TimedMessage`."); self.messager = Some(messager.clone()); tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; - trace!("Synced state for `TimedMessage`."); + trace!("Started `TimedMessage`."); return Box::pin(messager.stream()); } } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn echoer() { + std::env::set_var("RUST_LOG", "trace"); + tracing_subscriber::fmt::init(); let mut world = World::new("world"); let agent = Agent::builder(AGENT_ID).unwrap(); diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index b3d442e21..dfc5012c5 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -113,7 +113,7 @@ where E: DeserializeOwned + Send + Sync + 'static, { /// Creates a new [`Engine`] with the given [`Behavior`] and [`Receiver`]. - pub(crate) fn new(behavior: B, event_receiver: Receiver) -> Self { + pub(crate) fn new(behavior: B) -> Self { Self { behavior: Some(behavior), state: State::Uninitialized, @@ -147,8 +147,10 @@ where trace!("Behavior is processing."); let mut behavior = self.behavior.take().unwrap(); let mut stream = self.event_stream.take().unwrap(); + println!("STREAM IS GOING INTO BEHAVIOR."); let behavior_task = tokio::spawn(async move { while let Some(event) = stream.next().await { + println!("GOT AN EVENT: {:?}", event); let halt_option = behavior.process(event).await; if halt_option.is_some() { break; diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index fbba159c8..1755fb32a 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -15,18 +15,11 @@ //! The world module contains the core world abstraction for the Arbiter Engine. -use arbiter_core::{ - environment::{Environment, EnvironmentBuilder}, - middleware::RevmMiddleware, -}; +use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; use futures_util::future::join_all; -use tokio::sync::broadcast::Sender as BroadcastSender; use tracing::info; -use self::{ - agent::AgentBuilder, - machine::{MachineHalt, MachineInstruction}, -}; +use self::{agent::AgentBuilder, machine::MachineInstruction}; use super::*; use crate::{ agent::Agent, @@ -70,8 +63,6 @@ pub struct World { /// The agents in the world. pub agents: Option>, - agent_distributors: Option>>, - /// The environment for the world. pub environment: Environment, @@ -86,7 +77,6 @@ impl World { id: id.to_owned(), state: State::Uninitialized, agents: Some(HashMap::new()), - agent_distributors: None, environment: Environment::builder().build(), messager: Messager::new(), } @@ -98,7 +88,6 @@ impl World { id: id.to_owned(), agents: Some(HashMap::new()), state: State::Uninitialized, - agent_distributors: None, environment, messager: Messager::new(), } @@ -121,26 +110,12 @@ impl World { } } -// TODO: Idea, when we enter the `State::Processing`, we should pass the task -// into the struct. When we call `MachineInstruction::Stop` we should do message -// passing that will kill the tasks so that they return. This will allow us to -// do graceful shutdowns. - -// TODO: Worth explaining how the process stage is offloaded so it is -// understandable. - -// Right now what we do is we send a HALT message via the agent's distributor -// which means all behaviors should receive this now. If those behaviors all see -// this HALT message and then exit their process, then the await should finish. -// Actually we can probably not have to get the distributors up this high, but -// let's work with this for now. - #[async_trait::async_trait] impl StateMachine for World { async fn execute(&mut self, instruction: MachineInstruction) { match instruction { MachineInstruction::Start(_, _) => { - info!("World is syncing."); + info!("World is starting."); self.state = State::Starting; let agents = self.agents.take().unwrap(); let agent_tasks = join_all(agents.into_values().map(|mut agent| { @@ -165,16 +140,13 @@ impl StateMachine for World { info!("World is processing."); self.state = State::Processing; let agents = self.agents.take().unwrap(); - let mut agent_distributors = vec![]; let agent_processors = join_all(agents.into_values().map(|mut agent| { - agent_distributors.push(agent.distributor.0.clone()); let instruction_clone = instruction.clone(); tokio::spawn(async move { agent.execute(instruction_clone).await; agent }) })); - self.agent_distributors = Some(agent_distributors); self.agents = Some( agent_processors .await From e0ba125421f6d09679872fe515ff43b2d4d28c37 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 1 Feb 2024 10:58:30 -0700 Subject: [PATCH 16/38] save state wip --- arbiter-engine/src/examples/timed_message.rs | 243 ++++++++++--------- arbiter-engine/src/machine.rs | 2 - 2 files changed, 124 insertions(+), 121 deletions(-) diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 5cd368dec..43512baab 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -24,6 +24,7 @@ struct TimedMessage { messager: Option, count: u64, max_count: Option, + startup_message: Option, } impl TimedMessage { @@ -32,6 +33,7 @@ impl TimedMessage { receive_data: String, send_data: String, max_count: Option, + startup_message: Option, ) -> Self { Self { delay, @@ -40,6 +42,7 @@ impl TimedMessage { messager: None, count: 0, max_count, + startup_message, } } } @@ -78,6 +81,16 @@ impl Behavior for TimedMessage { self.messager = Some(messager.clone()); tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; trace!("Started `TimedMessage`."); + if let Some(startup_message) = &self.startup_message { + messager + .clone() + .send(Message { + from: messager.id.clone().unwrap(), + to: To::All, + data: startup_message.clone(), + }) + .await; + } return Box::pin(messager.stream()); } } @@ -94,127 +107,119 @@ async fn echoer() { "Hello, world!".to_owned(), "Hello, world!".to_owned(), Some(2), + Some("Hello, world!".to_owned()), ); world.add_agent(agent.with_behavior(behavior)); - let messager = world.messager.join_with_id(Some("god".to_owned())); - let task = world.run(); - - let message = Message { - from: "god".to_owned(), - to: To::Agent("agent".to_owned()), - data: "Hello, world!".to_owned(), - }; - messager.send(message).await; - task.await; - - let mut stream = Box::pin(messager.stream()); - let mut idx = 0; - - loop { - match timeout(Duration::from_secs(1), stream.next()).await { - Ok(Some(event)) => { - println!("Event received in outside world: {:?}", event); - idx += 1; - if idx == 2 { - break; - } - } - _ => { - panic!("Timeout reached. Test failed."); - } - } - } + world.run().await; + + // let mut stream = Box::pin(messager.stream()); + // let mut idx = 0; + + // loop { + // match timeout(Duration::from_secs(1), stream.next()).await { + // Ok(Some(event)) => { + // println!("Event received in outside world: {:?}", event); + // idx += 1; + // if idx == 2 { + // break; + // } + // } + // _ => { + // panic!("Timeout reached. Test failed."); + // } + // } + // } } -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn ping_pong() { - let mut world = World::new("world"); - - let agent = Agent::builder(AGENT_ID).unwrap(); - let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2)); - let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2)); - world.add_agent( - agent - .with_behavior(behavior_ping) - .with_behavior(behavior_pong), - ); - - let messager = world.messager.join_with_id(Some("god".to_owned())); - let task = world.run(); - - let init_message = Message { - from: "god".to_owned(), - to: To::Agent("agent".to_owned()), - data: "ping".to_owned(), - }; - messager.send(init_message).await; - - task.await; - - let mut stream = Box::pin(messager.stream()); - let mut idx = 0; - - loop { - match timeout(Duration::from_secs(1), stream.next()).await { - Ok(Some(event)) => { - println!("Event received in outside world: {:?}", event); - idx += 1; - if idx == 4 { - break; - } - } - _ => { - panic!("Timeout reached. Test failed."); - } - } - } -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn ping_pong_two_agent() { - let mut world = World::new("world"); - - let behavior_ping = TimedMessage::new(1, "pong".to_owned(), "ping".to_owned(), Some(2)); - let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2)); - let agent_ping = Agent::builder("agent_ping") - .unwrap() - .with_behavior(behavior_ping); - let agent_pong = Agent::builder("agent_pong") - .unwrap() - .with_behavior(behavior_pong); - - world.add_agent(agent_ping); - world.add_agent(agent_pong); - - let messager = world.messager.join_with_id(Some("god".to_owned())); - let task = world.run(); - - let init_message = Message { - from: "god".to_owned(), - to: To::All, - data: "ping".to_owned(), - }; - - messager.send(init_message).await; - - task.await; - - let mut stream = Box::pin(messager.stream()); - let mut idx = 0; - - loop { - match timeout(Duration::from_secs(1), stream.next()).await { - Ok(Some(event)) => { - println!("Event received in outside world: {:?}", event); - idx += 1; - if idx == 5 { - break; - } - } - _ => { - panic!("Timeout reached. Test failed."); - } - } - } -} +// #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +// async fn ping_pong() { +// let mut world = World::new("world"); + +// let agent = Agent::builder(AGENT_ID).unwrap(); +// let behavior_ping = TimedMessage::new(1, "pong".to_owned(), +// "ping".to_owned(), Some(2)); let behavior_pong = TimedMessage::new(1, +// "ping".to_owned(), "pong".to_owned(), Some(2)); world.add_agent( +// agent +// .with_behavior(behavior_ping) +// .with_behavior(behavior_pong), +// ); + +// let messager = world.messager.join_with_id(Some("god".to_owned())); +// let task = world.run(); + +// let init_message = Message { +// from: "god".to_owned(), +// to: To::Agent("agent".to_owned()), +// data: "ping".to_owned(), +// }; +// messager.send(init_message).await; + +// task.await; + +// let mut stream = Box::pin(messager.stream()); +// let mut idx = 0; + +// loop { +// match timeout(Duration::from_secs(1), stream.next()).await { +// Ok(Some(event)) => { +// println!("Event received in outside world: {:?}", event); +// idx += 1; +// if idx == 4 { +// break; +// } +// } +// _ => { +// panic!("Timeout reached. Test failed."); +// } +// } +// } +// } + +// #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +// async fn ping_pong_two_agent() { +// let mut world = World::new("world"); + +// let behavior_ping = TimedMessage::new(1, "pong".to_owned(), +// "ping".to_owned(), Some(2)); let behavior_pong = TimedMessage::new(1, +// "ping".to_owned(), "pong".to_owned(), Some(2)); let agent_ping = +// Agent::builder("agent_ping") .unwrap() +// .with_behavior(behavior_ping); +// let agent_pong = Agent::builder("agent_pong") +// .unwrap() +// .with_behavior(behavior_pong); + +// world.add_agent(agent_ping); +// world.add_agent(agent_pong); + +// let messager = world.messager.join_with_id(Some("god".to_owned())); +// let task = world.run(); + +// let init_message = Message { +// from: "god".to_owned(), +// to: To::All, +// data: "ping".to_owned(), +// }; + +// messager.send(init_message).await; + +// task.await; + +// let mut stream = Box::pin(messager.stream()); +// let mut idx = 0; + +// loop { +// match timeout(Duration::from_secs(1), stream.next()).await { +// Ok(Some(event)) => { +// println!("Event received in outside world: {:?}", event); +// idx += 1; +// if idx == 5 { +// break; +// } +// } +// _ => { +// panic!("Timeout reached. Test failed."); +// } +// } +// } +// } diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index dfc5012c5..27622c3ef 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -147,10 +147,8 @@ where trace!("Behavior is processing."); let mut behavior = self.behavior.take().unwrap(); let mut stream = self.event_stream.take().unwrap(); - println!("STREAM IS GOING INTO BEHAVIOR."); let behavior_task = tokio::spawn(async move { while let Some(event) = stream.next().await { - println!("GOT AN EVENT: {:?}", event); let halt_option = behavior.process(event).await; if halt_option.is_some() { break; From 1f4f232b8e8549339ebbad24f13b39b40b3a2bd9 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 1 Feb 2024 11:11:30 -0700 Subject: [PATCH 17/38] making some progress --- arbiter-engine/src/agent.rs | 29 ++-------------- arbiter-engine/src/machine.rs | 4 +-- arbiter-engine/src/world.rs | 62 ++++++----------------------------- 3 files changed, 15 insertions(+), 80 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index a744567d5..5cf181bf8 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -80,7 +80,7 @@ pub struct Agent { /// The messager the agent uses to send and receive messages from other /// agents. - pub messager: Option, + pub messager: Messager, /// The client the agent uses to interact with the blockchain. pub client: Arc, @@ -91,7 +91,7 @@ pub struct Agent { /// The engines/behaviors that the agent uses to sync, startup, and process /// events. - behavior_engines: Vec>, + pub(crate) behavior_engines: Vec>, } impl Agent { @@ -169,7 +169,7 @@ impl AgentBuilder { Some(engines) => Ok(Agent { id: self.id, state: State::Uninitialized, - messager: Some(messager), + messager, client, event_streamer: Some(EventLogger::builder()), behavior_engines: engines, @@ -179,29 +179,6 @@ impl AgentBuilder { } } -#[async_trait::async_trait] -impl StateMachine for Agent { - #[tracing::instrument(skip(self), fields(id = self.id))] - async fn execute(&mut self, instruction: MachineInstruction) { - match instruction { - MachineInstruction::Start(_, _) => { - debug!("Agent is starting."); - self.state = State::Starting; - self.run(MachineInstruction::Start( - Some(self.client.clone()), - self.messager.clone(), - )) - .await; - } - MachineInstruction::Process => { - debug!("Agent is processing."); - self.state = State::Processing; - self.run(instruction).await; - } - } - } -} - #[cfg(test)] mod tests { use arbiter_bindings::bindings::arbiter_token::ArbiterToken; diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 27622c3ef..2f89a3ac7 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -21,7 +21,7 @@ use super::*; #[derive(Clone, Debug)] pub enum MachineInstruction { /// Used to make a [`StateMachine`] start up. - Start(Option>, Option), + Start(Arc, Messager), /// Used to make a [`StateMachine`] process events. /// This will offload the process into a task that can be halted by sending @@ -136,7 +136,7 @@ where self.state = State::Starting; let mut behavior = self.behavior.take().unwrap(); let behavior_task = tokio::spawn(async move { - let stream = behavior.startup(client.unwrap(), messager.unwrap()).await; + let stream = behavior.startup(client, messager).await; (stream, behavior) }); let (stream, behavior) = behavior_task.await.unwrap(); diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 1755fb32a..a52e7c382 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -16,6 +16,7 @@ //! The world module contains the core world abstraction for the Arbiter Engine. use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; +use ethers::core::k256::sha2::digest::Mac; use futures_util::future::join_all; use tracing::info; @@ -103,60 +104,17 @@ impl World { agents.insert(id.to_owned(), agent); } + // TODO: We shouldn't have to call `execute(process)` /// Runs the world through up to the [`State::Processing`] stage. pub async fn run(&mut self) { - self.execute(MachineInstruction::Start(None, None)).await; - self.execute(MachineInstruction::Process).await; - } -} - -#[async_trait::async_trait] -impl StateMachine for World { - async fn execute(&mut self, instruction: MachineInstruction) { - match instruction { - MachineInstruction::Start(_, _) => { - info!("World is starting."); - self.state = State::Starting; - let agents = self.agents.take().unwrap(); - let agent_tasks = join_all(agents.into_values().map(|mut agent| { - let instruction_clone = instruction.clone(); - tokio::spawn(async move { - agent.execute(instruction_clone).await; - agent - }) - })); - self.agents = Some( - agent_tasks - .await - .into_iter() - .map(|res| { - let agent = res.unwrap(); - (agent.id.clone(), agent) - }) - .collect::>(), - ); - } - MachineInstruction::Process => { - info!("World is processing."); - self.state = State::Processing; - let agents = self.agents.take().unwrap(); - let agent_processors = join_all(agents.into_values().map(|mut agent| { - let instruction_clone = instruction.clone(); - tokio::spawn(async move { - agent.execute(instruction_clone).await; - agent - }) - })); - self.agents = Some( - agent_processors - .await - .into_iter() - .map(|res| { - let agent = res.unwrap(); - (agent.id.clone(), agent) - }) - .collect::>(), - ); + for agent in self.agents.take().unwrap().values_mut() { + for mut engine in agent.behavior_engines.drain(..) { + engine + .execute(MachineInstruction::Start( + agent.client.clone(), + agent.messager.clone(), + )) + .await; } } } From c2a964e8dc604084a46ba896aa7a70d74565b99b Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 1 Feb 2024 11:33:41 -0700 Subject: [PATCH 18/38] niiiiice --- arbiter-engine/src/agent.rs | 7 +--- arbiter-engine/src/examples/timed_message.rs | 2 +- arbiter-engine/src/machine.rs | 2 ++ arbiter-engine/src/world.rs | 36 ++++++++------------ 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 5cf181bf8..990fab552 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -85,10 +85,6 @@ pub struct Agent { /// The client the agent uses to interact with the blockchain. pub client: Arc, - /// The generalized event streamer for the agent that can stream a JSON - /// `String`of any Ethereum event that can be decoded by behaviors. - pub event_streamer: Option, - /// The engines/behaviors that the agent uses to sync, startup, and process /// events. pub(crate) behavior_engines: Vec>, @@ -162,8 +158,8 @@ impl AgentBuilder { /// Produces a new agent with the given identifier. pub fn build( self, - messager: Messager, client: Arc, + messager: Messager, ) -> Result { match self.behavior_engines { Some(engines) => Ok(Agent { @@ -171,7 +167,6 @@ impl AgentBuilder { state: State::Uninitialized, messager, client, - event_streamer: Some(EventLogger::builder()), behavior_engines: engines, }), None => Err(AgentBuildError::MissingBehaviorEngines), diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 43512baab..976a5491b 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -80,7 +80,6 @@ impl Behavior for TimedMessage { trace!("Starting up `TimedMessage`."); self.messager = Some(messager.clone()); tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; - trace!("Started `TimedMessage`."); if let Some(startup_message) = &self.startup_message { messager .clone() @@ -91,6 +90,7 @@ impl Behavior for TimedMessage { }) .await; } + trace!("Started `TimedMessage`."); return Box::pin(messager.stream()); } } diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 2f89a3ac7..3d9191c09 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -142,6 +142,8 @@ where let (stream, behavior) = behavior_task.await.unwrap(); self.event_stream = Some(stream); self.behavior = Some(behavior); + // TODO: This feels weird but I think it works properly? + self.execute(MachineInstruction::Process).await; } MachineInstruction::Process => { trace!("Behavior is processing."); diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index a52e7c382..8780c78d5 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -18,6 +18,7 @@ use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; use ethers::core::k256::sha2::digest::Mac; use futures_util::future::join_all; +use tokio::{spawn, task::JoinSet}; use tracing::info; use self::{agent::AgentBuilder, machine::MachineInstruction}; @@ -83,40 +84,33 @@ impl World { } } - /// Creates a new [World] with the given identifier and provider. - pub fn new_with_env(id: &str, environment: Environment) -> Self { - Self { - id: id.to_owned(), - agents: Some(HashMap::new()), - state: State::Uninitialized, - environment, - messager: Messager::new(), - } - } - /// Adds an agent to the world. pub fn add_agent(&mut self, agent_builder: AgentBuilder) { let id = agent_builder.id.clone(); - let messager = self.messager.for_agent(&id); let client = RevmMiddleware::new(&self.environment, Some(&id)).unwrap(); - let agent = agent_builder.build(messager, client).unwrap(); + let messager = self.messager.for_agent(&id); + let agent = agent_builder.build(client, messager).unwrap(); let agents = self.agents.as_mut().unwrap(); agents.insert(id.to_owned(), agent); } - // TODO: We shouldn't have to call `execute(process)` /// Runs the world through up to the [`State::Processing`] stage. pub async fn run(&mut self) { - for agent in self.agents.take().unwrap().values_mut() { + let mut tasks = vec![]; + // TODO: This unwrap should be checked. + let agents = self.agents.take().unwrap(); + for (_, mut agent) in agents { for mut engine in agent.behavior_engines.drain(..) { - engine - .execute(MachineInstruction::Start( - agent.client.clone(), - agent.messager.clone(), - )) - .await; + let client = agent.client.clone(); + let messager = agent.messager.clone(); + tasks.push(spawn(async move { + engine + .execute(MachineInstruction::Start(client, messager)) + .await + })); } } + join_all(tasks).await; } } From 780a136baaf2e8344dbda980e6e5302c66e23951 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 1 Feb 2024 15:28:28 -0500 Subject: [PATCH 19/38] wip: token_minter/requester test --- arbiter-engine/src/agent.rs | 15 - arbiter-engine/src/examples/timed_message.rs | 251 ++++--- arbiter-engine/src/examples/token_minter.rs | 722 +++++++++---------- arbiter-engine/src/machine.rs | 3 + arbiter-engine/src/messager.rs | 9 + 5 files changed, 494 insertions(+), 506 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 990fab552..be194f6df 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -98,21 +98,6 @@ impl Agent { behavior_engines: None, }) } - - pub(crate) async fn run(&mut self, instruction: MachineInstruction) { - let behavior_tasks = join_all(self.behavior_engines.drain(..).map(|mut engine| { - let instruction_clone = instruction.clone(); - tokio::spawn(async move { - engine.execute(instruction_clone).await; - engine - }) - })); - self.behavior_engines = behavior_tasks - .await - .into_iter() - .map(|res| res.unwrap()) - .collect::>(); - } } /// enum representing the possible error states encountered by the agent builder diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 976a5491b..7abd43efc 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -49,6 +49,28 @@ impl TimedMessage { #[async_trait::async_trait] impl Behavior for TimedMessage { + async fn startup( + &mut self, + _client: Arc, + messager: Messager, + ) -> Pin + Send + Sync>> { + trace!("Starting up `TimedMessage`."); + self.messager = Some(messager.clone()); + tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; + if let Some(startup_message) = &self.startup_message { + messager + .clone() + .send(Message { + from: messager.id.clone().unwrap(), + to: To::All, + data: startup_message.clone(), + }) + .await; + } + trace!("Started `TimedMessage`."); + return Box::pin(messager.stream()); + } + async fn process(&mut self, event: Message) -> Option { trace!("Processing event."); let messager = self.messager.as_ref().unwrap(); @@ -71,28 +93,6 @@ impl Behavior for TimedMessage { trace!("Processed event."); None } - - async fn startup( - &mut self, - _client: Arc, - messager: Messager, - ) -> Pin + Send + Sync>> { - trace!("Starting up `TimedMessage`."); - self.messager = Some(messager.clone()); - tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; - if let Some(startup_message) = &self.startup_message { - messager - .clone() - .send(Message { - from: messager.id.clone().unwrap(), - to: To::All, - data: startup_message.clone(), - }) - .await; - } - trace!("Started `TimedMessage`."); - return Box::pin(messager.stream()); - } } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] @@ -110,116 +110,107 @@ async fn echoer() { Some("Hello, world!".to_owned()), ); world.add_agent(agent.with_behavior(behavior)); + let messager = world.messager.join_with_id(None); world.run().await; - // let mut stream = Box::pin(messager.stream()); - // let mut idx = 0; - - // loop { - // match timeout(Duration::from_secs(1), stream.next()).await { - // Ok(Some(event)) => { - // println!("Event received in outside world: {:?}", event); - // idx += 1; - // if idx == 2 { - // break; - // } - // } - // _ => { - // panic!("Timeout reached. Test failed."); - // } - // } - // } + let mut stream = Box::pin(messager.stream()); + let mut idx = 0; + + loop { + match timeout(Duration::from_secs(1), stream.next()).await { + Ok(Some(event)) => { + println!("Event received in outside world: {:?}", event); + idx += 1; + if idx == 2 { + break; + } + } + _ => { + panic!("Timeout reached. Test failed."); + } + } + } } -// #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -// async fn ping_pong() { -// let mut world = World::new("world"); - -// let agent = Agent::builder(AGENT_ID).unwrap(); -// let behavior_ping = TimedMessage::new(1, "pong".to_owned(), -// "ping".to_owned(), Some(2)); let behavior_pong = TimedMessage::new(1, -// "ping".to_owned(), "pong".to_owned(), Some(2)); world.add_agent( -// agent -// .with_behavior(behavior_ping) -// .with_behavior(behavior_pong), -// ); - -// let messager = world.messager.join_with_id(Some("god".to_owned())); -// let task = world.run(); - -// let init_message = Message { -// from: "god".to_owned(), -// to: To::Agent("agent".to_owned()), -// data: "ping".to_owned(), -// }; -// messager.send(init_message).await; - -// task.await; - -// let mut stream = Box::pin(messager.stream()); -// let mut idx = 0; - -// loop { -// match timeout(Duration::from_secs(1), stream.next()).await { -// Ok(Some(event)) => { -// println!("Event received in outside world: {:?}", event); -// idx += 1; -// if idx == 4 { -// break; -// } -// } -// _ => { -// panic!("Timeout reached. Test failed."); -// } -// } -// } -// } - -// #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -// async fn ping_pong_two_agent() { -// let mut world = World::new("world"); - -// let behavior_ping = TimedMessage::new(1, "pong".to_owned(), -// "ping".to_owned(), Some(2)); let behavior_pong = TimedMessage::new(1, -// "ping".to_owned(), "pong".to_owned(), Some(2)); let agent_ping = -// Agent::builder("agent_ping") .unwrap() -// .with_behavior(behavior_ping); -// let agent_pong = Agent::builder("agent_pong") -// .unwrap() -// .with_behavior(behavior_pong); - -// world.add_agent(agent_ping); -// world.add_agent(agent_pong); - -// let messager = world.messager.join_with_id(Some("god".to_owned())); -// let task = world.run(); - -// let init_message = Message { -// from: "god".to_owned(), -// to: To::All, -// data: "ping".to_owned(), -// }; - -// messager.send(init_message).await; - -// task.await; - -// let mut stream = Box::pin(messager.stream()); -// let mut idx = 0; - -// loop { -// match timeout(Duration::from_secs(1), stream.next()).await { -// Ok(Some(event)) => { -// println!("Event received in outside world: {:?}", event); -// idx += 1; -// if idx == 5 { -// break; -// } -// } -// _ => { -// panic!("Timeout reached. Test failed."); -// } -// } -// } -// } +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn ping_pong() { + let mut world = World::new("world"); + + let agent = Agent::builder(AGENT_ID).unwrap(); + let behavior_ping = TimedMessage::new( + 1, + "pong".to_owned(), + "ping".to_owned(), + Some(2), + Some("ping".to_owned()), + ); + let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2), None); + world.add_agent( + agent + .with_behavior(behavior_ping) + .with_behavior(behavior_pong), + ); + + let messager = world.messager.join_with_id(Some("god".to_owned())); + world.run().await; + + let mut stream = Box::pin(messager.stream()); + let mut idx = 0; + + loop { + match timeout(Duration::from_secs(1), stream.next()).await { + Ok(Some(event)) => { + println!("Event received in outside world: {:?}", event); + idx += 1; + if idx == 4 { + break; + } + } + _ => { + panic!("Timeout reached. Test failed."); + } + } + } +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn ping_pong_two_agent() { + let mut world = World::new("world"); + + let agent_ping = Agent::builder("agent_ping").unwrap(); + let agent_pong = Agent::builder("agent_pong").unwrap(); + + let behavior_ping = TimedMessage::new( + 1, + "pong".to_owned(), + "ping".to_owned(), + Some(2), + Some("ping".to_owned()), + ); + let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2), None); + + world.add_agent(agent_ping.with_behavior(behavior_ping)); + world.add_agent(agent_pong.with_behavior(behavior_pong)); + + let messager = world.messager.join_with_id(Some("god".to_owned())); + world.run().await; + + let mut stream = Box::pin(messager.stream()); + let mut idx = 0; + + loop { + match timeout(Duration::from_secs(1), stream.next()).await { + Ok(Some(event)) => { + println!("Event received in outside world: {:?}", event); + idx += 1; + if idx == 5 { + break; + } + } + _ => { + panic!("Timeout reached. Test failed."); + } + } + } +} diff --git a/arbiter-engine/src/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs index 7420fd7f0..b6d4bf716 100644 --- a/arbiter-engine/src/examples/token_minter.rs +++ b/arbiter-engine/src/examples/token_minter.rs @@ -1,361 +1,361 @@ -// use std::{str::FromStr, time::Duration}; - -// use anyhow::Context; -// use arbiter_bindings::bindings::arbiter_token; -// use arbiter_core::data_collection::EventLogger; -// use ethers::{ -// abi::token, -// types::{transaction::request, Filter}, -// }; -// use tokio::time::timeout; -// use tracing::error; - -// use self::machine::MachineHalt; -// use super::*; -// use crate::{ -// agent::Agent, -// machine::{Behavior, MachineInstruction, StateMachine}, -// messager::To, -// world::World, -// }; - -// const TOKEN_ADMIN_ID: &str = "token_admin"; -// const REQUESTER_ID: &str = "requester"; -// const TOKEN_NAME: &str = "Arbiter Token"; -// const TOKEN_SYMBOL: &str = "ARB"; -// const TOKEN_DECIMALS: u8 = 18; - -// /// The token admin is responsible for handling token minting requests. -// #[derive(Debug)] -// pub struct TokenAdmin { -// /// The identifier of the token admin. -// pub token_data: HashMap, - -// pub tokens: Option>>, - -// pub client: Option>, - -// pub messager: Option, - -// count: u64, - -// max_count: Option, -// } - -// impl TokenAdmin { -// pub fn new(count: u64, max_count: Option) -> Self { -// Self { -// token_data: HashMap::new(), -// tokens: None, -// client: None, -// messager: None, -// count, -// max_count, -// } -// } - -// /// Adds a token to the token admin. -// pub fn add_token(&mut self, token_data: TokenData) { -// self.token_data.insert(token_data.name.clone(), token_data); -// } -// } - -// #[derive(Clone, Debug, Deserialize, Serialize)] -// pub struct TokenData { -// pub name: String, -// pub symbol: String, -// pub decimals: u8, -// pub address: Option
, -// } - -// /// Used as an action to ask what tokens are available. -// #[derive(Clone, Debug, Deserialize, Serialize)] -// pub enum TokenAdminQuery { -// /// Get the address of the token. -// AddressOf(String), - -// /// Mint tokens. -// MintRequest(MintRequest), -// } - -// /// Used as an action to mint tokens. -// #[derive(Clone, Debug, Deserialize, Serialize)] -// pub struct MintRequest { -// /// The token to mint. -// pub token: String, - -// /// The address to mint to. -// pub mint_to: Address, - -// /// The amount to mint. -// pub mint_amount: u64, -// } - -// #[async_trait::async_trait] -// impl Behavior for TokenAdmin { -// #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] -// async fn sync(&mut self, messager: Messager, client: Arc) -// { for token_data in self.token_data.values_mut() { -// let token = ArbiterToken::deploy( -// client.clone(), -// ( -// token_data.name.clone(), -// token_data.symbol.clone(), -// token_data.decimals, -// ), -// ) -// .unwrap() -// .send() -// .await -// .unwrap(); -// token_data.address = Some(token.address()); -// self.tokens -// .get_or_insert_with(HashMap::new) -// .insert(token_data.name.clone(), token.clone()); -// debug!("Deployed token: {:?}", token); -// } -// self.messager = Some(messager); -// self.client = Some(client); -// } - -// #[tracing::instrument(skip(self), fields(id = -// self.messager.as_ref().unwrap().id.as_deref()))] async fn process(&mut -// self, event: Message) -> Option { if -// self.tokens.is_none() { error!( -// "There were no tokens to deploy! You must add tokens to -// the token admin before running the simulation." -// ); -// } - -// let query: TokenAdminQuery = -// serde_json::from_str(&event.data).unwrap(); trace!("Got query: {:?}", -// query); let messager = self.messager.as_ref().unwrap(); -// match query { -// TokenAdminQuery::AddressOf(token_name) => { -// trace!( -// "Getting address of token with name: {:?}", -// token_name.clone() -// ); -// let token_data = self.token_data.get(&token_name).unwrap(); -// let message = Message { -// from: messager.id.clone().unwrap(), -// to: To::Agent(event.from.clone()), // Reply back to -// sender data: serde_json::to_string(token_data).unwrap(), -// }; -// messager.send(message).await; -// } -// TokenAdminQuery::MintRequest(mint_request) => { -// trace!("Minting tokens: {:?}", mint_request); -// let token = self -// .tokens -// .as_ref() -// .unwrap() -// .get(&mint_request.token) -// .unwrap(); -// token -// .mint(mint_request.mint_to, -// U256::from(mint_request.mint_amount)) .send() -// .await -// .unwrap() -// .await -// .unwrap(); -// self.count += 1; -// if self.count == self.max_count.unwrap_or(u64::MAX) { -// warn!("Reached max count. Halting behavior."); -// return Some(MachineHalt); -// } -// } -// } -// None -// } -// } - -// /// The token requester is responsible for requesting tokens from the token -// /// admin. This agents is purely for testing purposes as far as I can tell. -// #[derive(Debug)] -// pub struct TokenRequester { -// /// The tokens that the token requester has requested. -// pub token_data: TokenData, - -// /// The agent ID to request tokens to. -// pub request_to: String, - -// /// Client to have an address to receive token mint to and check balance -// pub client: Option>, - -// /// The messaging layer for the token requester. -// pub messager: Option, - -// pub count: u64, - -// pub max_count: Option, -// } - -// impl TokenRequester { -// pub fn new(count: u64, max_count: Option) -> Self { -// Self { -// token_data: TokenData { -// name: TOKEN_NAME.to_owned(), -// symbol: TOKEN_SYMBOL.to_owned(), -// decimals: TOKEN_DECIMALS, -// address: None, -// }, -// request_to: TOKEN_ADMIN_ID.to_owned(), -// client: None, -// messager: None, -// count, -// max_count, -// } -// } -// } - -// #[async_trait::async_trait] -// impl Behavior for TokenRequester { -// #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] -// async fn sync(&mut self, messager: Messager, client: Arc) -// { self.messager = Some(messager); -// self.client = Some(client); -// } - -// #[tracing::instrument(skip(self), fields(id = -// self.messager.as_ref().unwrap().id.as_deref()))] async fn startup(&mut -// self) { let messager = self.messager.as_ref().unwrap(); -// trace!("Requesting address of token: {:?}", self.token_data.name); -// let message = Message { -// from: messager.id.clone().unwrap(), -// to: To::Agent(self.request_to.clone()), -// data: -// serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name. -// clone())) .unwrap(), -// }; -// messager.send(message).await; -// } - -// #[tracing::instrument(skip(self), fields(id = -// self.messager.as_ref().unwrap().id.as_deref()))] async fn process(&mut -// self, event: Message) -> Option { if let Ok(token_data) -// = serde_json::from_str::(&event.data) { let messager = -// self.messager.as_ref().unwrap(); trace!( -// "Got -// token data: {:?}", -// token_data -// ); -// trace!( -// "Requesting first mint of -// token: {:?}", -// self.token_data.name -// ); -// let message = Message { -// from: messager.id.clone().unwrap(), -// to: To::Agent(self.request_to.clone()), -// data: -// serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { -// token: self.token_data.name.clone(), mint_to: -// self.client.as_ref().unwrap().address(), mint_amount: 1, -// })) -// .unwrap(), -// }; -// messager.send(message).await; -// } -// Some(MachineHalt) -// } -// } -// // world.run() -> sync state -> iterate over agents, `execute` each agent -> -// // agent sync state -> iterate over behaviors -> behavior sync state -// #[async_trait::async_trait] -// impl Behavior for TokenRequester { -// async fn sync(&mut self, messager: Messager, client: Arc) -// { self.client = Some(client); -// self.messager = Some(messager); -// } - -// #[tracing::instrument(skip(self), fields(id = -// self.messager.as_ref().unwrap().id.as_deref()))] async fn process(&mut -// self, event: arbiter_token::TransferFilter) -> Option { -// let messager = self.messager.as_ref().unwrap(); -// trace!( -// "Got event for -// `TokenRequester` logger: {:?}", -// event -// ); -// std::thread::sleep(std::time::Duration::from_secs(1)); -// let message = Message { -// from: messager.id.clone().unwrap(), -// to: To::Agent(self.request_to.clone()), -// data: -// serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { -// token: self.token_data.name.clone(), mint_to: -// self.client.as_ref().unwrap().address(), mint_amount: 1, -// })) -// .unwrap(), -// }; -// messager.send(message).await; -// self.count += 1; -// if self.count == self.max_count.unwrap_or(u64::MAX) { -// warn!("Reached max count. Halting behavior."); -// return Some(MachineHalt); -// } -// None -// } -// } - -// #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -// async fn token_minter_simulation() { -// // 3. have a method on world to update mutable map of addresses -// let mut world = World::new("test_world"); -// // self.contracts: HashMap - -// // Create the token admin agent -// // 1. use agent builder struct to get rid of reference to world -// let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); -// let mut token_admin_behavior = TokenAdmin::new(0, Some(4)); -// token_admin_behavior.add_token(TokenData { -// name: TOKEN_NAME.to_owned(), -// symbol: TOKEN_SYMBOL.to_owned(), -// decimals: TOKEN_DECIMALS, -// address: None, -// }); -// world.add_agent(token_admin.with_behavior(token_admin_behavior)); - -// // Create the token requester agent -// let token_requester = Agent::builder(REQUESTER_ID).unwrap(); -// let token_requester_behavior = TokenRequester::new(0, Some(4)); -// // 2. appropriately handle event driven behaviors -// // let arb = ArbiterToken::new( -// // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f"). -// unwrap(), // token_requester.client.clone(), -// // ); -// // let transfer_event = arb.transfer_filter(); -// // -// // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); -// // world.add_agent( -// // token_requester -// // .with_behavior::(token_requester_behavior) -// // .with_behavior::(token_requester_behavior_again) -// // .with_event(transfer_event), -// // ); -// // -// // let transfer_stream = EventLogger::builder() -// // .add_stream(arb.transfer_filter()) -// // .stream() -// // .unwrap(); -// // let mut stream = Box::pin(transfer_stream); -// // let mut idx = 0; -// // -// // world.run().await; -// // -// // loop { -// // match timeout(Duration::from_secs(1), stream.next()).await { -// // Ok(Some(event)) => { -// // println!("Event received in outside world: {:?}", event); -// // idx += 1; -// // if idx == 4 { -// // break; -// // } -// // } -// // _ => { -// // panic!("Timeout reached. Test failed."); -// // } -// // } -// // } -// } +use std::{pin::Pin, str::FromStr, time::Duration}; + +use anyhow::Context; +use arbiter_bindings::bindings::arbiter_token::{ArbiterToken, TransferFilter}; +use arbiter_core::data_collection::EventLogger; +use ethers::{ + abi::token, + types::{transaction::request, Filter}, +}; +use futures_util::Stream; +use tokio::time::timeout; +use tracing::error; + +use self::machine::MachineHalt; +use super::*; +use crate::{ + agent::Agent, + machine::{Behavior, MachineInstruction, StateMachine}, + messager::To, + world::World, +}; + +const TOKEN_ADMIN_ID: &str = "token_admin"; +const REQUESTER_ID: &str = "requester"; +const TOKEN_NAME: &str = "Arbiter Token"; +const TOKEN_SYMBOL: &str = "ARB"; +const TOKEN_DECIMALS: u8 = 18; + +/// The token admin is responsible for handling token minting requests. +#[derive(Debug)] +pub struct TokenAdmin { + /// The identifier of the token admin. + pub token_data: HashMap, + + pub tokens: Option>>, + + pub client: Option>, + + pub messager: Option, + + count: u64, + + max_count: Option, + startup_message: Option, +} + +impl TokenAdmin { + pub fn new(max_count: Option) -> Self { + Self { + token_data: HashMap::new(), + tokens: None, + client: None, + messager: None, + count: 0, + max_count, + startup_message: None, + } + } + + /// Adds a token to the token admin. + pub fn add_token(&mut self, token_data: TokenData) { + self.token_data.insert(token_data.name.clone(), token_data); + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TokenData { + pub name: String, + pub symbol: String, + pub decimals: u8, + pub address: Option
, +} + +/// Used as an action to ask what tokens are available. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum TokenAdminQuery { + /// Get the address of the token. + AddressOf(String), + + /// Mint tokens. + MintRequest(MintRequest), +} + +/// Used as an action to mint tokens. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MintRequest { + /// The token to mint. + pub token: String, + + /// The address to mint to. + pub mint_to: Address, + + /// The amount to mint. + pub mint_amount: u64, +} + +#[async_trait::async_trait] +impl Behavior for TokenAdmin { + #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] + async fn startup( + &mut self, + client: Arc, + messager: Messager, + ) -> Pin + Send + Sync>> { + self.messager = Some(messager.clone()); + self.client = Some(client.clone()); + for token_data in self.token_data.values_mut() { + let token = ArbiterToken::deploy( + client.clone(), + ( + token_data.name.clone(), + token_data.symbol.clone(), + token_data.decimals, + ), + ) + .unwrap() + .send() + .await + .unwrap(); + + token_data.address = Some(token.address()); + self.tokens + .get_or_insert_with(HashMap::new) + .insert(token_data.name.clone(), token.clone()); + } + Box::pin(messager.stream()) + } + + #[tracing::instrument(skip(self), fields(id = + self.messager.as_ref().unwrap().id.as_deref()))] + async fn process(&mut self, event: Message) -> Option { + if self.tokens.is_none() { + error!( + "There were no tokens to deploy! You must add tokens to + the token admin before running the simulation." + ); + } + + let query: TokenAdminQuery = serde_json::from_str(&event.data).unwrap(); + trace!("Got query: {:?}", query); + let messager = self.messager.as_ref().unwrap(); + match query { + TokenAdminQuery::AddressOf(token_name) => { + trace!( + "Getting address of token with name: {:?}", + token_name.clone() + ); + let token_data = self.token_data.get(&token_name).unwrap(); + let message = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(event.from.clone()), // Reply back to sender + data: serde_json::to_string(token_data).unwrap(), + }; + messager.send(message).await; + } + TokenAdminQuery::MintRequest(mint_request) => { + trace!("Minting tokens: {:?}", mint_request); + let token = self + .tokens + .as_ref() + .unwrap() + .get(&mint_request.token) + .unwrap(); + token + .mint(mint_request.mint_to, U256::from(mint_request.mint_amount)) + .send() + .await + .unwrap() + .await + .unwrap(); + self.count += 1; + if self.count == self.max_count.unwrap_or(u64::MAX) { + warn!("Reached max count. Halting behavior."); + return Some(MachineHalt); + } + } + } + None + } +} + +/// The token requester is responsible for requesting tokens from the token +/// admin. This agents is purely for testing purposes as far as I can tell. +#[derive(Debug)] +pub struct TokenRequester { + /// The tokens that the token requester has requested. + pub token_data: TokenData, + + /// The agent ID to request tokens to. + pub request_to: String, + + /// Client to have an address to receive token mint to and check balance + pub client: Option>, + + /// The messaging layer for the token requester. + pub messager: Option, + + pub count: u64, + + pub max_count: Option, +} + +impl TokenRequester { + pub fn new(max_count: Option) -> Self { + Self { + token_data: TokenData { + name: TOKEN_NAME.to_owned(), + symbol: TOKEN_SYMBOL.to_owned(), + decimals: TOKEN_DECIMALS, + address: None, + }, + request_to: TOKEN_ADMIN_ID.to_owned(), + client: None, + messager: None, + count: 0, + max_count, + } + } +} + +#[async_trait::async_trait] +impl Behavior for TokenRequester { + #[tracing::instrument(skip(self), fields(id = + self.messager.as_ref().unwrap().id.as_deref()))] + async fn startup( + &mut self, + client: Arc, + messager: Messager, + ) -> Pin + Send + Sync>> { + debug!("inside of token requester startup!"); + self.messager = Some(messager.clone()); + self.client = Some(client.clone()); + + let messager = self.messager.as_mut().unwrap(); + + let message = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(self.request_to.clone()), + data: serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name.clone())) + .unwrap(), + }; + let token_address = messager.get_next().await.data; + let token = ArbiterToken::new(Address::from_str(&token_address).unwrap(), client.clone()); + let name = token.name().call().await.unwrap(); + let symbol = token.symbol().call().await.unwrap(); + let decimals = token.decimals().call().await.unwrap(); + + self.token_data = TokenData { + name, + symbol, + decimals, + address: Some(token.address()), + }; + + let mint_data = serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { + token: token.name().call().await.unwrap(), + mint_to: client.address(), + mint_amount: 1, + })) + .unwrap(); + + let mint_request = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(self.request_to.clone()), + data: mint_data, + }; + + messager.send(mint_request).await; + + return Box::pin( + EventLogger::builder() + .add_stream(token.transfer_filter()) + .stream() + .unwrap() + .map(|value| serde_json::from_str(&value).unwrap()), + ); + } + + #[tracing::instrument(skip(self), fields(id = + self.messager.as_ref().unwrap().id.as_deref()))] + async fn process(&mut self, event: TransferFilter) -> Option { + let messager = self.messager.as_ref().unwrap(); + let message = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(self.request_to.clone()), + data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { + token: self.token_data.name.clone(), + mint_to: self.client.as_ref().unwrap().address(), + mint_amount: 1, + })) + .unwrap(), + }; + messager.send(message).await; + Some(MachineHalt) + } +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn token_minter_simulation() { + std::env::set_var("RUST_LOG", "trace"); + tracing_subscriber::fmt::init(); + // 3. have a method on world to update mutable map of addresses + let mut world = World::new("test_world"); + // self.contracts: HashMap + + let token_requester = Agent::builder(REQUESTER_ID).unwrap(); + let mut token_requester_behavior = TokenRequester::new(Some(4)); + world.add_agent(token_requester.with_behavior(token_requester_behavior)); + + // Create the token admin agent + /* + let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); + let mut token_admin_behavior = TokenAdmin::new(Some(4)); + token_admin_behavior.add_token(TokenData { + name: TOKEN_NAME.to_owned(), + symbol: TOKEN_SYMBOL.to_owned(), + decimals: TOKEN_DECIMALS, + address: None, + }); + world.add_agent(token_admin.with_behavior(token_admin_behavior)); + */ + + world.run().await; + // 2. appropriately handle event driven behaviors + // let arb = ArbiterToken::new( + // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), // token_requester.client.clone(), + // ); + // let transfer_event = arb.transfer_filter(); + // + // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); + // world.add_agent( + // token_requester + // .with_behavior::(token_requester_behavior) + // .with_behavior::(token_requester_behavior_again) + // .with_event(transfer_event), + // ); + // + // let transfer_stream = EventLogger::builder() + // .add_stream(arb.transfer_filter()) + // .stream() + // .unwrap(); + // let mut stream = Box::pin(transfer_stream); + // let mut idx = 0; + // + // world.run().await; + // + // loop { + // match timeout(Duration::from_secs(1), stream.next()).await { + // Ok(Some(event)) => { + // println!("Event received in outside world: {:?}", event); + // idx += 1; + // if idx == 4 { + // break; + // } + // } + // _ => { + // panic!("Timeout reached. Test failed."); + // } + // } + // } +} diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 3d9191c09..0f153d9d6 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -136,7 +136,10 @@ where self.state = State::Starting; let mut behavior = self.behavior.take().unwrap(); let behavior_task = tokio::spawn(async move { + let id = messager.id.clone(); + debug!("starting up stream for {:?}!", id); let stream = behavior.startup(client, messager).await; + debug!("startup complete for {:?}!", id); (stream, behavior) }); let (stream, behavior) = behavior_task.await.unwrap(); diff --git a/arbiter-engine/src/messager.rs b/arbiter-engine/src/messager.rs index 041b7ccd6..37f655a72 100644 --- a/arbiter-engine/src/messager.rs +++ b/arbiter-engine/src/messager.rs @@ -75,6 +75,15 @@ impl Messager { } } + pub async fn get_next(&mut self) -> Message { + self.broadcast_receiver + .as_mut() + .unwrap() + .recv() + .await + .unwrap() + } + /// Returns a stream of messages that are either sent to [`To::All`] or to /// the agent via [`To::Agent(id)`]. pub fn stream(mut self) -> impl Stream + Send { From 30943d6430a3ef878dd9cd7f7a84059d3271c86b Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 1 Feb 2024 15:24:39 -0700 Subject: [PATCH 20/38] fix: tracing instrument for startup --- arbiter-engine/src/examples/token_minter.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/arbiter-engine/src/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs index b6d4bf716..28ce4e33d 100644 --- a/arbiter-engine/src/examples/token_minter.rs +++ b/arbiter-engine/src/examples/token_minter.rs @@ -41,6 +41,7 @@ pub struct TokenAdmin { count: u64, max_count: Option, + startup_message: Option, } @@ -220,8 +221,7 @@ impl TokenRequester { #[async_trait::async_trait] impl Behavior for TokenRequester { - #[tracing::instrument(skip(self), fields(id = - self.messager.as_ref().unwrap().id.as_deref()))] + #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] async fn startup( &mut self, client: Arc, @@ -308,7 +308,6 @@ async fn token_minter_simulation() { world.add_agent(token_requester.with_behavior(token_requester_behavior)); // Create the token admin agent - /* let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); let mut token_admin_behavior = TokenAdmin::new(Some(4)); token_admin_behavior.add_token(TokenData { @@ -318,13 +317,12 @@ async fn token_minter_simulation() { address: None, }); world.add_agent(token_admin.with_behavior(token_admin_behavior)); - */ world.run().await; // 2. appropriately handle event driven behaviors // let arb = ArbiterToken::new( - // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), // token_requester.client.clone(), - // ); + // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), + // // token_requester.client.clone(), ); // let transfer_event = arb.transfer_filter(); // // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); From a43ed07c6ae7ef14fdd5898925823f4710cf3520 Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 1 Feb 2024 16:01:10 -0700 Subject: [PATCH 21/38] fix: messager and stuff --- arbiter-engine/src/examples/token_minter.rs | 31 ++++++++------------- arbiter-engine/src/messager.rs | 22 +++++++++++---- arbiter-engine/src/world.rs | 10 ++++++- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/arbiter-engine/src/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs index 28ce4e33d..1410f606a 100644 --- a/arbiter-engine/src/examples/token_minter.rs +++ b/arbiter-engine/src/examples/token_minter.rs @@ -150,7 +150,7 @@ impl Behavior for TokenAdmin { let message = Message { from: messager.id.clone().unwrap(), to: To::Agent(event.from.clone()), // Reply back to sender - data: serde_json::to_string(token_data).unwrap(), + data: serde_json::to_string(&token_data.address).unwrap(), }; messager.send(message).await; } @@ -225,32 +225,22 @@ impl Behavior for TokenRequester { async fn startup( &mut self, client: Arc, - messager: Messager, + mut messager: Messager, ) -> Pin + Send + Sync>> { debug!("inside of token requester startup!"); - self.messager = Some(messager.clone()); - self.client = Some(client.clone()); - - let messager = self.messager.as_mut().unwrap(); - let message = Message { from: messager.id.clone().unwrap(), to: To::Agent(self.request_to.clone()), data: serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name.clone())) .unwrap(), }; - let token_address = messager.get_next().await.data; - let token = ArbiterToken::new(Address::from_str(&token_address).unwrap(), client.clone()); - let name = token.name().call().await.unwrap(); - let symbol = token.symbol().call().await.unwrap(); - let decimals = token.decimals().call().await.unwrap(); - - self.token_data = TokenData { - name, - symbol, - decimals, - address: Some(token.address()), - }; + messager.send(message).await; + + let message = messager.get_next().await; + let token_address = serde_json::from_str::
(&message.data).unwrap(); + let token = ArbiterToken::new(token_address, client.clone()); + + self.token_data.address = Some(token_address); let mint_data = serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { token: token.name().call().await.unwrap(), @@ -267,6 +257,9 @@ impl Behavior for TokenRequester { messager.send(mint_request).await; + self.messager = Some(messager.clone()); + self.client = Some(client.clone()); + return Box::pin( EventLogger::builder() .add_stream(token.transfer_filter()) diff --git a/arbiter-engine/src/messager.rs b/arbiter-engine/src/messager.rs index 37f655a72..cd4d80a2e 100644 --- a/arbiter-engine/src/messager.rs +++ b/arbiter-engine/src/messager.rs @@ -76,12 +76,22 @@ impl Messager { } pub async fn get_next(&mut self) -> Message { - self.broadcast_receiver - .as_mut() - .unwrap() - .recv() - .await - .unwrap() + while let Ok(message) = self.broadcast_receiver.as_mut().unwrap().recv().await { + match &message.to { + To::All => { + return message; + } + To::Agent(id) => { + if let Some(self_id) = &self.id { + if id == self_id { + return message; + } + } + continue; + } + } + } + unreachable!() } /// Returns a stream of messages that are either sent to [`To::All`] or to diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 8780c78d5..62f309b09 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -15,6 +15,8 @@ //! The world module contains the core world abstraction for the Arbiter Engine. +use std::collections::VecDeque; + use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; use ethers::core::k256::sha2::digest::Mac; use futures_util::future::join_all; @@ -99,10 +101,16 @@ impl World { let mut tasks = vec![]; // TODO: This unwrap should be checked. let agents = self.agents.take().unwrap(); + let mut messagers = VecDeque::new(); + for (_, agent) in agents.iter() { + for _ in &agent.behavior_engines { + messagers.push_back(agent.messager.clone()); + } + } for (_, mut agent) in agents { for mut engine in agent.behavior_engines.drain(..) { let client = agent.client.clone(); - let messager = agent.messager.clone(); + let messager = messagers.pop_front().unwrap(); tasks.push(spawn(async move { engine .execute(MachineInstruction::Start(client, messager)) From 54b162525c226f4ca973fe72e0d74fb1eab27e03 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 1 Feb 2024 18:54:47 -0500 Subject: [PATCH 22/38] refactor: token_minter test cleanup --- .../src/examples/minter/agents/mod.rs | 3 + .../src/examples/minter/agents/token_admin.rs | 32 ++ .../examples/minter/agents/token_requester.rs | 35 ++ .../src/examples/minter/behaviors/mod.rs | 3 + .../examples/minter/behaviors/token_admin.rs | 111 ++++++ .../minter/behaviors/token_requester.rs | 92 +++++ arbiter-engine/src/examples/minter/mod.rs | 28 ++ .../src/examples/minter/token_minter.rs | 66 ++++ arbiter-engine/src/examples/mod.rs | 2 +- arbiter-engine/src/examples/token_minter.rs | 352 ------------------ arbiter-engine/src/machine.rs | 2 +- 11 files changed, 372 insertions(+), 354 deletions(-) create mode 100644 arbiter-engine/src/examples/minter/agents/mod.rs create mode 100644 arbiter-engine/src/examples/minter/agents/token_admin.rs create mode 100644 arbiter-engine/src/examples/minter/agents/token_requester.rs create mode 100644 arbiter-engine/src/examples/minter/behaviors/mod.rs create mode 100644 arbiter-engine/src/examples/minter/behaviors/token_admin.rs create mode 100644 arbiter-engine/src/examples/minter/behaviors/token_requester.rs create mode 100644 arbiter-engine/src/examples/minter/mod.rs create mode 100644 arbiter-engine/src/examples/minter/token_minter.rs delete mode 100644 arbiter-engine/src/examples/token_minter.rs diff --git a/arbiter-engine/src/examples/minter/agents/mod.rs b/arbiter-engine/src/examples/minter/agents/mod.rs new file mode 100644 index 000000000..7db494eed --- /dev/null +++ b/arbiter-engine/src/examples/minter/agents/mod.rs @@ -0,0 +1,3 @@ +use super::*; +pub mod token_admin; +pub mod token_requester; diff --git a/arbiter-engine/src/examples/minter/agents/token_admin.rs b/arbiter-engine/src/examples/minter/agents/token_admin.rs new file mode 100644 index 000000000..f3ef512cf --- /dev/null +++ b/arbiter-engine/src/examples/minter/agents/token_admin.rs @@ -0,0 +1,32 @@ +use super::*; + +#[derive(Debug)] +pub struct TokenAdmin { + /// The identifier of the token admin. + pub token_data: HashMap, + pub tokens: Option>>, + pub client: Option>, + pub messager: Option, + pub count: u64, + pub max_count: Option, + startup_message: Option, +} + +impl TokenAdmin { + pub fn new(max_count: Option) -> Self { + Self { + token_data: HashMap::new(), + tokens: None, + client: None, + messager: None, + count: 0, + max_count, + startup_message: None, + } + } + + /// Adds a token to the token admin. + pub fn add_token(&mut self, token_data: TokenData) { + self.token_data.insert(token_data.name.clone(), token_data); + } +} diff --git a/arbiter-engine/src/examples/minter/agents/token_requester.rs b/arbiter-engine/src/examples/minter/agents/token_requester.rs new file mode 100644 index 000000000..9fa40d9fe --- /dev/null +++ b/arbiter-engine/src/examples/minter/agents/token_requester.rs @@ -0,0 +1,35 @@ +use super::*; + +/// The token requester is responsible for requesting tokens from the token +/// admin. This agents is purely for testing purposes as far as I can tell. +#[derive(Debug)] +pub struct TokenRequester { + /// The tokens that the token requester has requested. + pub token_data: TokenData, + /// The agent ID to request tokens to. + pub request_to: String, + /// Client to have an address to receive token mint to and check balance + pub client: Option>, + /// The messaging layer for the token requester. + pub messager: Option, + pub count: u64, + pub max_count: Option, +} + +impl TokenRequester { + pub fn new(max_count: Option) -> Self { + Self { + token_data: TokenData { + name: TOKEN_NAME.to_owned(), + symbol: TOKEN_SYMBOL.to_owned(), + decimals: TOKEN_DECIMALS, + address: None, + }, + request_to: TOKEN_ADMIN_ID.to_owned(), + client: None, + messager: None, + count: 0, + max_count, + } + } +} diff --git a/arbiter-engine/src/examples/minter/behaviors/mod.rs b/arbiter-engine/src/examples/minter/behaviors/mod.rs new file mode 100644 index 000000000..7db494eed --- /dev/null +++ b/arbiter-engine/src/examples/minter/behaviors/mod.rs @@ -0,0 +1,3 @@ +use super::*; +pub mod token_admin; +pub mod token_requester; diff --git a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs new file mode 100644 index 000000000..b79a8f750 --- /dev/null +++ b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs @@ -0,0 +1,111 @@ +use self::examples::minter::agents::token_admin::TokenAdmin; + +use super::*; + +/// Used as an action to ask what tokens are available. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum TokenAdminQuery { + /// Get the address of the token. + AddressOf(String), + + /// Mint tokens. + MintRequest(MintRequest), +} + +/// Used as an action to mint tokens. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MintRequest { + /// The token to mint. + pub token: String, + + /// The address to mint to. + pub mint_to: Address, + + /// The amount to mint. + pub mint_amount: u64, +} + +#[async_trait::async_trait] +impl Behavior for TokenAdmin { + #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] + async fn startup( + &mut self, + client: Arc, + messager: Messager, + ) -> Pin + Send + Sync>> { + self.messager = Some(messager.clone()); + self.client = Some(client.clone()); + for token_data in self.token_data.values_mut() { + let token = ArbiterToken::deploy( + client.clone(), + ( + token_data.name.clone(), + token_data.symbol.clone(), + token_data.decimals, + ), + ) + .unwrap() + .send() + .await + .unwrap(); + + token_data.address = Some(token.address()); + self.tokens + .get_or_insert_with(HashMap::new) + .insert(token_data.name.clone(), token.clone()); + } + Box::pin(messager.stream()) + } + + #[tracing::instrument(skip(self), fields(id = + self.messager.as_ref().unwrap().id.as_deref()))] + async fn process(&mut self, event: Message) -> Option { + if self.tokens.is_none() { + error!( + "There were no tokens to deploy! You must add tokens to + the token admin before running the simulation." + ); + } + + let query: TokenAdminQuery = serde_json::from_str(&event.data).unwrap(); + trace!("Got query: {:?}", query); + let messager = self.messager.as_ref().unwrap(); + match query { + TokenAdminQuery::AddressOf(token_name) => { + trace!( + "Getting address of token with name: {:?}", + token_name.clone() + ); + let token_data = self.token_data.get(&token_name).unwrap(); + let message = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(event.from.clone()), // Reply back to sender + data: serde_json::to_string(&token_data.address).unwrap(), + }; + messager.send(message).await; + } + TokenAdminQuery::MintRequest(mint_request) => { + trace!("Minting tokens: {:?}", mint_request); + let token = self + .tokens + .as_ref() + .unwrap() + .get(&mint_request.token) + .unwrap(); + token + .mint(mint_request.mint_to, U256::from(mint_request.mint_amount)) + .send() + .await + .unwrap() + .await + .unwrap(); + self.count += 1; + if self.count == self.max_count.unwrap_or(u64::MAX) { + warn!("Reached max count. Halting behavior."); + return Some(MachineHalt); + } + } + } + None + } +} diff --git a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs new file mode 100644 index 000000000..fd2f2ed64 --- /dev/null +++ b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs @@ -0,0 +1,92 @@ +use self::examples::minter::agents::token_requester::TokenRequester; +use super::*; +use arbiter_bindings::bindings::arbiter_token::{ArbiterToken, TransferFilter}; +use arbiter_core::data_collection::EventLogger; +use std::pin::Pin; +use token_admin::{MintRequest, TokenAdminQuery}; + +#[async_trait::async_trait] +impl Behavior for TokenRequester { + #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] + async fn startup( + &mut self, + client: Arc, + mut messager: Messager, + ) -> Pin + Send + Sync>> { + debug!("inside of token requester startup!"); + let message = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(self.request_to.clone()), + data: serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name.clone())) + .unwrap(), + }; + messager.send(message).await; + let message = messager.get_next().await; + let token_address = serde_json::from_str::
(&message.data).unwrap(); + let token = ArbiterToken::new(token_address, client.clone()); + self.token_data.address = Some(token_address); + debug!("token addr: {:?}", token_address); + + let mint_data = serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { + token: token.name().call().await.unwrap(), + mint_to: client.address(), + mint_amount: 1, + })) + .unwrap(); + + let mint_request = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(self.request_to.clone()), + data: mint_data, + }; + + messager.send(mint_request).await; + /* + + let mint_request = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(self.request_to.clone()), + data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { + token: self.token_data.name.clone(), + mint_to: self.client.as_ref().unwrap().address(), + mint_amount: 1, + })) + .unwrap(), + }; + messager.send(mint_request).await; + */ + debug!("message sent successfully"); + + self.messager = Some(messager.clone()); + self.client = Some(client.clone()); + return Box::pin( + EventLogger::builder() + .add_stream(token.transfer_filter()) + .stream() + .unwrap() + .map(|value| serde_json::from_str(&value).unwrap()), + ); + } + + #[tracing::instrument(skip(self), fields(id = + self.messager.as_ref().unwrap().id.as_deref()))] + async fn process(&mut self, event: TransferFilter) -> Option { + let messager = self.messager.as_ref().unwrap(); + while (self.count < self.max_count.unwrap()) { + debug!("sending message from requester"); + let message = Message { + from: messager.id.clone().unwrap(), + to: To::Agent(self.request_to.clone()), + data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { + token: self.token_data.name.clone(), + mint_to: self.client.as_ref().unwrap().address(), + mint_amount: 1, + })) + .unwrap(), + }; + messager.send(message).await; + self.count += 1; + } + Some(MachineHalt) + } +} diff --git a/arbiter-engine/src/examples/minter/mod.rs b/arbiter-engine/src/examples/minter/mod.rs new file mode 100644 index 000000000..861cd02d7 --- /dev/null +++ b/arbiter-engine/src/examples/minter/mod.rs @@ -0,0 +1,28 @@ +use super::*; +pub mod agents; +pub mod behaviors; +pub mod token_minter; + +use crate::{ + agent::Agent, + machine::{Behavior, MachineHalt, MachineInstruction, StateMachine}, + messager::To, + world::World, +}; +use futures_util::Stream; +use std::pin::Pin; +use tracing::error; + +const TOKEN_ADMIN_ID: &str = "token_admin"; +const REQUESTER_ID: &str = "requester"; +const TOKEN_NAME: &str = "Arbiter Token"; +const TOKEN_SYMBOL: &str = "ARB"; +const TOKEN_DECIMALS: u8 = 18; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TokenData { + pub name: String, + pub symbol: String, + pub decimals: u8, + pub address: Option
, +} diff --git a/arbiter-engine/src/examples/minter/token_minter.rs b/arbiter-engine/src/examples/minter/token_minter.rs new file mode 100644 index 000000000..79ad4550f --- /dev/null +++ b/arbiter-engine/src/examples/minter/token_minter.rs @@ -0,0 +1,66 @@ +use super::*; +use crate::world::World; +use agents::{token_admin::TokenAdmin, token_requester::TokenRequester}; + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn token_minter_simulation() { + std::env::set_var("RUST_LOG", "trace"); + tracing_subscriber::fmt::init(); + // 3. have a method on world to update mutable map of addresses + let mut world = World::new("test_world"); + // self.contracts: HashMap + + let token_requester = Agent::builder(REQUESTER_ID).unwrap(); + let mut token_requester_behavior = TokenRequester::new(Some(4)); + world.add_agent(token_requester.with_behavior(token_requester_behavior)); + + // Create the token admin agent + let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); + let mut token_admin_behavior = TokenAdmin::new(Some(4)); + token_admin_behavior.add_token(TokenData { + name: TOKEN_NAME.to_owned(), + symbol: TOKEN_SYMBOL.to_owned(), + decimals: TOKEN_DECIMALS, + address: None, + }); + world.add_agent(token_admin.with_behavior(token_admin_behavior)); + + world.run().await; + // 2. appropriately handle event driven behaviors + // let arb = ArbiterToken::new( + // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), + // // token_requester.client.clone(), ); + // let transfer_event = arb.transfer_filter(); + // + // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); + // world.add_agent( + // token_requester + // .with_behavior::(token_requester_behavior) + // .with_behavior::(token_requester_behavior_again) + // .with_event(transfer_event), + // ); + // + // let transfer_stream = EventLogger::builder() + // .add_stream(arb.transfer_filter()) + // .stream() + // .unwrap(); + // let mut stream = Box::pin(transfer_stream); + // let mut idx = 0; + // + // world.run().await; + // + // loop { + // match timeout(Duration::from_secs(1), stream.next()).await { + // Ok(Some(event)) => { + // println!("Event received in outside world: {:?}", event); + // idx += 1; + // if idx == 4 { + // break; + // } + // } + // _ => { + // panic!("Timeout reached. Test failed."); + // } + // } + // } +} diff --git a/arbiter-engine/src/examples/mod.rs b/arbiter-engine/src/examples/mod.rs index 5ca2c05e4..cb7653b28 100644 --- a/arbiter-engine/src/examples/mod.rs +++ b/arbiter-engine/src/examples/mod.rs @@ -18,5 +18,5 @@ use futures_util::{stream, StreamExt}; use super::*; use crate::messager::{Message, Messager}; +mod minter; mod timed_message; -mod token_minter; diff --git a/arbiter-engine/src/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs deleted file mode 100644 index 1410f606a..000000000 --- a/arbiter-engine/src/examples/token_minter.rs +++ /dev/null @@ -1,352 +0,0 @@ -use std::{pin::Pin, str::FromStr, time::Duration}; - -use anyhow::Context; -use arbiter_bindings::bindings::arbiter_token::{ArbiterToken, TransferFilter}; -use arbiter_core::data_collection::EventLogger; -use ethers::{ - abi::token, - types::{transaction::request, Filter}, -}; -use futures_util::Stream; -use tokio::time::timeout; -use tracing::error; - -use self::machine::MachineHalt; -use super::*; -use crate::{ - agent::Agent, - machine::{Behavior, MachineInstruction, StateMachine}, - messager::To, - world::World, -}; - -const TOKEN_ADMIN_ID: &str = "token_admin"; -const REQUESTER_ID: &str = "requester"; -const TOKEN_NAME: &str = "Arbiter Token"; -const TOKEN_SYMBOL: &str = "ARB"; -const TOKEN_DECIMALS: u8 = 18; - -/// The token admin is responsible for handling token minting requests. -#[derive(Debug)] -pub struct TokenAdmin { - /// The identifier of the token admin. - pub token_data: HashMap, - - pub tokens: Option>>, - - pub client: Option>, - - pub messager: Option, - - count: u64, - - max_count: Option, - - startup_message: Option, -} - -impl TokenAdmin { - pub fn new(max_count: Option) -> Self { - Self { - token_data: HashMap::new(), - tokens: None, - client: None, - messager: None, - count: 0, - max_count, - startup_message: None, - } - } - - /// Adds a token to the token admin. - pub fn add_token(&mut self, token_data: TokenData) { - self.token_data.insert(token_data.name.clone(), token_data); - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct TokenData { - pub name: String, - pub symbol: String, - pub decimals: u8, - pub address: Option
, -} - -/// Used as an action to ask what tokens are available. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum TokenAdminQuery { - /// Get the address of the token. - AddressOf(String), - - /// Mint tokens. - MintRequest(MintRequest), -} - -/// Used as an action to mint tokens. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct MintRequest { - /// The token to mint. - pub token: String, - - /// The address to mint to. - pub mint_to: Address, - - /// The amount to mint. - pub mint_amount: u64, -} - -#[async_trait::async_trait] -impl Behavior for TokenAdmin { - #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] - async fn startup( - &mut self, - client: Arc, - messager: Messager, - ) -> Pin + Send + Sync>> { - self.messager = Some(messager.clone()); - self.client = Some(client.clone()); - for token_data in self.token_data.values_mut() { - let token = ArbiterToken::deploy( - client.clone(), - ( - token_data.name.clone(), - token_data.symbol.clone(), - token_data.decimals, - ), - ) - .unwrap() - .send() - .await - .unwrap(); - - token_data.address = Some(token.address()); - self.tokens - .get_or_insert_with(HashMap::new) - .insert(token_data.name.clone(), token.clone()); - } - Box::pin(messager.stream()) - } - - #[tracing::instrument(skip(self), fields(id = - self.messager.as_ref().unwrap().id.as_deref()))] - async fn process(&mut self, event: Message) -> Option { - if self.tokens.is_none() { - error!( - "There were no tokens to deploy! You must add tokens to - the token admin before running the simulation." - ); - } - - let query: TokenAdminQuery = serde_json::from_str(&event.data).unwrap(); - trace!("Got query: {:?}", query); - let messager = self.messager.as_ref().unwrap(); - match query { - TokenAdminQuery::AddressOf(token_name) => { - trace!( - "Getting address of token with name: {:?}", - token_name.clone() - ); - let token_data = self.token_data.get(&token_name).unwrap(); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(event.from.clone()), // Reply back to sender - data: serde_json::to_string(&token_data.address).unwrap(), - }; - messager.send(message).await; - } - TokenAdminQuery::MintRequest(mint_request) => { - trace!("Minting tokens: {:?}", mint_request); - let token = self - .tokens - .as_ref() - .unwrap() - .get(&mint_request.token) - .unwrap(); - token - .mint(mint_request.mint_to, U256::from(mint_request.mint_amount)) - .send() - .await - .unwrap() - .await - .unwrap(); - self.count += 1; - if self.count == self.max_count.unwrap_or(u64::MAX) { - warn!("Reached max count. Halting behavior."); - return Some(MachineHalt); - } - } - } - None - } -} - -/// The token requester is responsible for requesting tokens from the token -/// admin. This agents is purely for testing purposes as far as I can tell. -#[derive(Debug)] -pub struct TokenRequester { - /// The tokens that the token requester has requested. - pub token_data: TokenData, - - /// The agent ID to request tokens to. - pub request_to: String, - - /// Client to have an address to receive token mint to and check balance - pub client: Option>, - - /// The messaging layer for the token requester. - pub messager: Option, - - pub count: u64, - - pub max_count: Option, -} - -impl TokenRequester { - pub fn new(max_count: Option) -> Self { - Self { - token_data: TokenData { - name: TOKEN_NAME.to_owned(), - symbol: TOKEN_SYMBOL.to_owned(), - decimals: TOKEN_DECIMALS, - address: None, - }, - request_to: TOKEN_ADMIN_ID.to_owned(), - client: None, - messager: None, - count: 0, - max_count, - } - } -} - -#[async_trait::async_trait] -impl Behavior for TokenRequester { - #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] - async fn startup( - &mut self, - client: Arc, - mut messager: Messager, - ) -> Pin + Send + Sync>> { - debug!("inside of token requester startup!"); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name.clone())) - .unwrap(), - }; - messager.send(message).await; - - let message = messager.get_next().await; - let token_address = serde_json::from_str::
(&message.data).unwrap(); - let token = ArbiterToken::new(token_address, client.clone()); - - self.token_data.address = Some(token_address); - - let mint_data = serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { - token: token.name().call().await.unwrap(), - mint_to: client.address(), - mint_amount: 1, - })) - .unwrap(); - - let mint_request = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: mint_data, - }; - - messager.send(mint_request).await; - - self.messager = Some(messager.clone()); - self.client = Some(client.clone()); - - return Box::pin( - EventLogger::builder() - .add_stream(token.transfer_filter()) - .stream() - .unwrap() - .map(|value| serde_json::from_str(&value).unwrap()), - ); - } - - #[tracing::instrument(skip(self), fields(id = - self.messager.as_ref().unwrap().id.as_deref()))] - async fn process(&mut self, event: TransferFilter) -> Option { - let messager = self.messager.as_ref().unwrap(); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { - token: self.token_data.name.clone(), - mint_to: self.client.as_ref().unwrap().address(), - mint_amount: 1, - })) - .unwrap(), - }; - messager.send(message).await; - Some(MachineHalt) - } -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn token_minter_simulation() { - std::env::set_var("RUST_LOG", "trace"); - tracing_subscriber::fmt::init(); - // 3. have a method on world to update mutable map of addresses - let mut world = World::new("test_world"); - // self.contracts: HashMap - - let token_requester = Agent::builder(REQUESTER_ID).unwrap(); - let mut token_requester_behavior = TokenRequester::new(Some(4)); - world.add_agent(token_requester.with_behavior(token_requester_behavior)); - - // Create the token admin agent - let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); - let mut token_admin_behavior = TokenAdmin::new(Some(4)); - token_admin_behavior.add_token(TokenData { - name: TOKEN_NAME.to_owned(), - symbol: TOKEN_SYMBOL.to_owned(), - decimals: TOKEN_DECIMALS, - address: None, - }); - world.add_agent(token_admin.with_behavior(token_admin_behavior)); - - world.run().await; - // 2. appropriately handle event driven behaviors - // let arb = ArbiterToken::new( - // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), - // // token_requester.client.clone(), ); - // let transfer_event = arb.transfer_filter(); - // - // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); - // world.add_agent( - // token_requester - // .with_behavior::(token_requester_behavior) - // .with_behavior::(token_requester_behavior_again) - // .with_event(transfer_event), - // ); - // - // let transfer_stream = EventLogger::builder() - // .add_stream(arb.transfer_filter()) - // .stream() - // .unwrap(); - // let mut stream = Box::pin(transfer_stream); - // let mut idx = 0; - // - // world.run().await; - // - // loop { - // match timeout(Duration::from_secs(1), stream.next()).await { - // Ok(Some(event)) => { - // println!("Event received in outside world: {:?}", event); - // idx += 1; - // if idx == 4 { - // break; - // } - // } - // _ => { - // panic!("Timeout reached. Test failed."); - // } - // } - // } -} diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 0f153d9d6..518f2818d 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -10,9 +10,9 @@ use std::{fmt::Debug, pin::Pin, sync::Arc}; use arbiter_core::middleware::RevmMiddleware; +use ethers::contract::{EthLogDecode, Event}; use futures_util::{Stream, StreamExt}; use serde::de::DeserializeOwned; -use tokio::sync::broadcast::Receiver; use self::messager::Messager; use super::*; From 70db06dd237d3df3157a862dd45158a8be39ec9b Mon Sep 17 00:00:00 2001 From: kinrezc Date: Fri, 2 Feb 2024 13:05:32 -0500 Subject: [PATCH 23/38] cleanup: finalize example tests, fix clippy errors/redundant imports --- Cargo.lock | 1 + arbiter-core/src/database.rs | 2 +- arbiter-engine/Cargo.toml | 1 + arbiter-engine/src/agent.rs | 162 ++---------------- .../minter/behaviors/token_requester.rs | 24 +-- .../src/examples/minter/token_minter.rs | 82 ++++----- arbiter-engine/src/examples/timed_message.rs | 8 +- arbiter-engine/src/machine.rs | 7 - arbiter-engine/src/messager.rs | 22 +-- arbiter-engine/src/world.rs | 10 +- 10 files changed, 65 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0e7d9609..3499b5cd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,6 +314,7 @@ dependencies = [ "futures-util", "serde", "serde_json", + "thiserror", "tokio", "tokio-stream", "tracing", diff --git a/arbiter-core/src/database.rs b/arbiter-core/src/database.rs index 423e6f428..812d970f1 100644 --- a/arbiter-core/src/database.rs +++ b/arbiter-core/src/database.rs @@ -227,7 +227,7 @@ mod tests { assert_eq!(account_a.info.nonce, 1234); assert_eq!(account_a.info.balance, U256::from(0xfacade)); assert_eq!(account_a.info.code, None); - assert_eq!(account_a.info.code_hash, keccak256(&[])); + assert_eq!(account_a.info.code_hash, keccak256([])); let account_b = db .load_account(address!("0000000000000000000000000000000000000001")) diff --git a/arbiter-engine/Cargo.toml b/arbiter-engine/Cargo.toml index fef73219b..57d14be30 100644 --- a/arbiter-engine/Cargo.toml +++ b/arbiter-engine/Cargo.toml @@ -26,6 +26,7 @@ futures = "0.3.30" crossbeam-channel.workspace = true arbiter-core.workspace = true arbiter-bindings.workspace = true +thiserror.workspace = true [dev-dependencies] arbiter-core.workspace = true diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index be194f6df..d73d1e2c2 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -1,38 +1,15 @@ -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// TODO: Notes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// * Maybe we just use tokio for everything (like `select`) so that we don't mix -// futures and tokio together in ways that may be weird. -// When we start running an agent, we should have their messager start producing -// events that can be used by any and all behaviors the agent has that takes in -// messages as an event. Similarly, we should have agents start up any streams -// listeners that they need so those can also produce events. Those can then be -// piped into the behaviors that need them. Can perhaps make behaviors come from -// very specific events (e.g., specific contract events). This means each -// behavior should be a consumer and perhaps the agent itself is the producer -// (or at least relayer). -// This means we should give agents some way to "start streams" that they can -// then use to produce events. -// Behaviors definitely need to be able to reference the agent's client and -// messager so that they can send messages and interact with the blockchain. -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - //! The agent module contains the core agent abstraction for the Arbiter Engine. use std::{fmt::Debug, sync::Arc}; -use arbiter_core::{data_collection::EventLogger, middleware::RevmMiddleware}; -use futures_util::future::join_all; +use arbiter_core::middleware::RevmMiddleware; use serde::de::DeserializeOwned; -use self::machine::MachineInstruction; -use super::*; use crate::{ machine::{Behavior, Engine, State, StateMachine}, messager::Messager, }; - -// TODO: For the time being, these agents are just meant to be for arbiter -// instances. We can generalize later. +use thiserror::Error; /// An agent is an entity capable of processing events and producing actions. /// These are the core actors in simulations or in onchain systems. @@ -61,15 +38,6 @@ use crate::{ /// 5. [`State::Stopped`]: The [`Agent`] is stopped. This is where the [`Agent`] /// can be stopped and state of the [`World`] and its [`Agent`]s can be /// offloaded and saved. -// todo(matt): use builder pattern where we just have the agent builder -// implement deserialize with just behavior_engines -// -// #[derive(Serialize, Deserialize)] -// pub struct AgentBuilder { -// pub id: String, -// pub behavior_engines: Option>>, -// pub world: &World -// } pub struct Agent { /// Identifier for this agent. /// Used for routing messages. @@ -100,23 +68,7 @@ impl Agent { } } -/// enum representing the possible error states encountered by the agent builder -#[derive(Debug)] -pub enum AgentBuildError { - MissingBehaviorEngines, -} - -impl std::error::Error for AgentBuildError {} -impl std::fmt::Display for AgentBuildError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AgentBuildError::MissingBehaviorEngines => { - write!(f, "Behavior engines must be set before building the agent") - } // ... other error variants - } - } -} - +/// [`AgentBuilder`] represents the intermediate state of agent creation before it is converted into a full on [`Agent`] pub struct AgentBuilder { /// Identifier for this agent. /// Used for routing messages. @@ -127,6 +79,7 @@ pub struct AgentBuilder { } impl AgentBuilder { + /// Appends a behavior onto an [`AgentBuilder`]. Behaviors are initialized when the agent builder is added to the [`crate::world::World`] pub fn with_behavior( mut self, behavior: impl Behavior + 'static, @@ -140,7 +93,7 @@ impl AgentBuilder { self } - /// Produces a new agent with the given identifier. + /// Produces a new [`Agent`] with the given identifier. pub fn build( self, client: Arc, @@ -159,103 +112,10 @@ impl AgentBuilder { } } -#[cfg(test)] -mod tests { - use arbiter_bindings::bindings::arbiter_token::ArbiterToken; - use ethers::types::U256; - - use super::*; - use crate::{messager::Message, world::World}; - - #[tokio::test(flavor = "multi_thread", worker_threads = 4)] - async fn streaming() { - // std::env::set_var("RUST_LOG", "trace"); - // tracing_subscriber::fmt::init(); - - let world = World::new("world"); - let agent = Agent::builder("agent").unwrap(); - // let arb = ArbiterToken::deploy( - // agent.client.clone(), - // ("ArbiterToken".to_string(), "ARB".to_string(), 18u8), - // ) - // .unwrap() - // .send() - // .await - // .unwrap(); - // - // let mut agent = agent.with_event(arb.events()); - // let address = agent.client.address(); - // - // TODO: (START BLOCK) It would be nice to get this block to be a single - // function that isn't copy and pasted from above. - // let messager = agent.messager.take().unwrap(); - // let message_stream = messager - // .stream() - // .map(|msg| serde_json::to_string(&msg).unwrap_or_else(|e| - // e.to_string())); let eth_event_stream = - // agent.event_streamer.take().unwrap().stream(); - // - // let mut event_stream: Pin + Send + '_>> - // = if let Some(event_stream) = eth_event_stream { - // trace!("Merging event streams."); - // let all_streams = vec![ - // Box::pin(message_stream) as Pin + - // Send>>, Box::pin(event_stream), - // ]; - // Box::pin(futures::stream::select_all(all_streams)) - // } else { - // trace!("Agent only sees message stream."); - // Box::pin(message_stream) - // }; - // TODO: (END BLOCK) - // - // let outside_messager = world.messager.join_with_id(None); - // let message_task = tokio::spawn(async move { - // for _ in 0..5 { - // outside_messager - // .send(Message { - // from: "god".to_string(), - // to: messager::To::All, - // data: "hello".to_string(), - // }) - // .await; - // } - // }); - // - // let eth_event_task = tokio::spawn(async move { - // for i in 0..5 { - // if i == 0 { - // tokio::time::sleep(std::time::Duration::from_secs(1)).await; - // } - // arb.approve(address, U256::from(1)) - // .send() - // .await - // .unwrap() - // .await - // .unwrap(); - // } - // }); - // - // let mut idx = 0; - // let print_task = tokio::spawn(async move { - // while let Some(msg) = event_stream.next().await { - // println!("Printing message in test: {:?}", msg); - // if idx < 5 { - // assert_eq!(msg, - // "{\"from\":\"god\",\"to\":\"All\",\"data\":\"hello\"}"); - // } else { - // assert_eq!(msg, - // "{\"ApprovalFilter\":{\"owner\":\" - // 0xe7a46f3d9f0e9b9c02f58f95e3bcee2db54050b0\",\"spender\":\" - // 0xe7a46f3d9f0e9b9c02f58f95e3bcee2db54050b0\",\"amount\":\"0x1\"}}"); - // } - // idx += 1; - // if idx == 10 { - // break; - // } - // } - // }); - // join_all(vec![message_task, eth_event_task, print_task]).await; - panic!() - } +/// enum representing the possible error states encountered by the agent builder +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum AgentBuildError { + /// Error representing the case where the agent is missing behavior engines; an agent has to have behaviors to be useful! + #[error("Agent is missing behavior engines")] + MissingBehaviorEngines, } diff --git a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs index fd2f2ed64..a460496dd 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs @@ -1,8 +1,7 @@ use self::examples::minter::agents::token_requester::TokenRequester; use super::*; -use arbiter_bindings::bindings::arbiter_token::{ArbiterToken, TransferFilter}; +use arbiter_bindings::bindings::arbiter_token::TransferFilter; use arbiter_core::data_collection::EventLogger; -use std::pin::Pin; use token_admin::{MintRequest, TokenAdminQuery}; #[async_trait::async_trait] @@ -13,7 +12,6 @@ impl Behavior for TokenRequester { client: Arc, mut messager: Messager, ) -> Pin + Send + Sync>> { - debug!("inside of token requester startup!"); let message = Message { from: messager.id.clone().unwrap(), to: To::Agent(self.request_to.clone()), @@ -25,37 +23,19 @@ impl Behavior for TokenRequester { let token_address = serde_json::from_str::
(&message.data).unwrap(); let token = ArbiterToken::new(token_address, client.clone()); self.token_data.address = Some(token_address); - debug!("token addr: {:?}", token_address); let mint_data = serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { - token: token.name().call().await.unwrap(), + token: self.token_data.name.clone(), mint_to: client.address(), mint_amount: 1, })) .unwrap(); - let mint_request = Message { from: messager.id.clone().unwrap(), to: To::Agent(self.request_to.clone()), data: mint_data, }; - - messager.send(mint_request).await; - /* - - let mint_request = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { - token: self.token_data.name.clone(), - mint_to: self.client.as_ref().unwrap().address(), - mint_amount: 1, - })) - .unwrap(), - }; messager.send(mint_request).await; - */ - debug!("message sent successfully"); self.messager = Some(messager.clone()); self.client = Some(client.clone()); diff --git a/arbiter-engine/src/examples/minter/token_minter.rs b/arbiter-engine/src/examples/minter/token_minter.rs index 79ad4550f..5e71b17af 100644 --- a/arbiter-engine/src/examples/minter/token_minter.rs +++ b/arbiter-engine/src/examples/minter/token_minter.rs @@ -1,18 +1,15 @@ use super::*; use crate::world::World; use agents::{token_admin::TokenAdmin, token_requester::TokenRequester}; +use arbiter_core::data_collection::EventLogger; +use ethers::types::Address; +use std::{str::FromStr, time::Duration}; +use tokio::time::timeout; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn token_minter_simulation() { - std::env::set_var("RUST_LOG", "trace"); - tracing_subscriber::fmt::init(); - // 3. have a method on world to update mutable map of addresses let mut world = World::new("test_world"); - // self.contracts: HashMap - - let token_requester = Agent::builder(REQUESTER_ID).unwrap(); - let mut token_requester_behavior = TokenRequester::new(Some(4)); - world.add_agent(token_requester.with_behavior(token_requester_behavior)); + let client = RevmMiddleware::new(&world.environment, None).unwrap(); // Create the token admin agent let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); @@ -23,44 +20,39 @@ async fn token_minter_simulation() { decimals: TOKEN_DECIMALS, address: None, }); + // Create the token requester agent + let token_requester = Agent::builder(REQUESTER_ID).unwrap(); + let mut token_requester_behavior = TokenRequester::new(Some(4)); + world.add_agent(token_requester.with_behavior(token_requester_behavior)); + world.add_agent(token_admin.with_behavior(token_admin_behavior)); + let arb = ArbiterToken::new( + Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), + client.clone(), + ); + let transfer_event = arb.transfer_filter(); + + let transfer_stream = EventLogger::builder() + .add_stream(arb.transfer_filter()) + .stream() + .unwrap(); + let mut stream = Box::pin(transfer_stream); world.run().await; - // 2. appropriately handle event driven behaviors - // let arb = ArbiterToken::new( - // Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), - // // token_requester.client.clone(), ); - // let transfer_event = arb.transfer_filter(); - // - // let token_requester_behavior_again = TokenRequester::new(0, Some(4)); - // world.add_agent( - // token_requester - // .with_behavior::(token_requester_behavior) - // .with_behavior::(token_requester_behavior_again) - // .with_event(transfer_event), - // ); - // - // let transfer_stream = EventLogger::builder() - // .add_stream(arb.transfer_filter()) - // .stream() - // .unwrap(); - // let mut stream = Box::pin(transfer_stream); - // let mut idx = 0; - // - // world.run().await; - // - // loop { - // match timeout(Duration::from_secs(1), stream.next()).await { - // Ok(Some(event)) => { - // println!("Event received in outside world: {:?}", event); - // idx += 1; - // if idx == 4 { - // break; - // } - // } - // _ => { - // panic!("Timeout reached. Test failed."); - // } - // } - // } + let mut idx = 0; + + loop { + match timeout(Duration::from_secs(1), stream.next()).await { + Ok(Some(event)) => { + println!("Event received in outside world: {:?}", event); + idx += 1; + if idx == 4 { + break; + } + } + _ => { + panic!("Timeout reached. Test failed."); + } + } + } } diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 7abd43efc..56fe2be65 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -97,8 +97,6 @@ impl Behavior for TimedMessage { #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn echoer() { - std::env::set_var("RUST_LOG", "trace"); - tracing_subscriber::fmt::init(); let mut world = World::new("world"); let agent = Agent::builder(AGENT_ID).unwrap(); @@ -110,7 +108,7 @@ async fn echoer() { Some("Hello, world!".to_owned()), ); world.add_agent(agent.with_behavior(behavior)); - let messager = world.messager.join_with_id(None); + let messager = world.messager.for_agent("outside_world"); world.run().await; @@ -152,7 +150,7 @@ async fn ping_pong() { .with_behavior(behavior_pong), ); - let messager = world.messager.join_with_id(Some("god".to_owned())); + let messager = world.messager.for_agent("outside_world"); world.run().await; let mut stream = Box::pin(messager.stream()); @@ -193,7 +191,7 @@ async fn ping_pong_two_agent() { world.add_agent(agent_ping.with_behavior(behavior_ping)); world.add_agent(agent_pong.with_behavior(behavior_pong)); - let messager = world.messager.join_with_id(Some("god".to_owned())); + let messager = world.messager.for_agent("outside_world"); world.run().await; let mut stream = Box::pin(messager.stream()); diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 518f2818d..c7229a334 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -1,16 +1,9 @@ //! The [`StateMachine`] trait, [`Behavior`] trait, and the [`Engine`] that runs //! [`Behavior`]s. -// TODO: Notes -// Could typestate pattern help here at all? Sync could produce a `Synced` state -// behavior that can then not have options for client and messager. Then the -// user can decide if they want to use those in their behavior and get a bit -// simpler UX. - use std::{fmt::Debug, pin::Pin, sync::Arc}; use arbiter_core::middleware::RevmMiddleware; -use ethers::contract::{EthLogDecode, Event}; use futures_util::{Stream, StreamExt}; use serde::de::DeserializeOwned; diff --git a/arbiter-engine/src/messager.rs b/arbiter-engine/src/messager.rs index cd4d80a2e..cc13c7a90 100644 --- a/arbiter-engine/src/messager.rs +++ b/arbiter-engine/src/messager.rs @@ -1,5 +1,9 @@ //! The messager module contains the core messager layer for the Arbiter Engine. +// TODO: Allow for modulating the capacity of the messager. +// TODO: It might be nice to have some kind of messaging header so that we can +// pipe messages to agents and pipe messages across worlds. + use futures_util::Stream; use tokio::sync::broadcast::{channel, Receiver, Sender}; @@ -51,9 +55,6 @@ impl Clone for Messager { } impl Messager { - // TODO: Allow for modulating the capacity of the messager. - // TODO: It might be nice to have some kind of messaging header so that we can - // pipe messages to agents and pipe messages across worlds. /// Creates a new messager with the given capacity. #[allow(clippy::new_without_default)] pub fn new() -> Self { @@ -65,8 +66,8 @@ impl Messager { } } - // TODO: Okay if we do something kinda like this, then agents don't even need to - // filter the `to` field or set the `from` field. Let's give this a shot! + /// Returns a [`Messager`] interface connected to the same instance but with + /// the `id` provided. pub(crate) fn for_agent(&self, id: &str) -> Self { Self { broadcast_sender: self.broadcast_sender.clone(), @@ -75,6 +76,7 @@ impl Messager { } } + /// utility function for getting the next value from the broadcast_receiver without streaming pub async fn get_next(&mut self) -> Message { while let Ok(message) = self.broadcast_receiver.as_mut().unwrap().recv().await { match &message.to { @@ -116,16 +118,6 @@ impl Messager { } } - /// Returns a [`Messager`] interface connected to the same instance but with - /// the `id` provided. - pub fn join_with_id(&self, id: Option) -> Messager { - Messager { - broadcast_sender: self.broadcast_sender.clone(), - broadcast_receiver: Some(self.broadcast_sender.subscribe()), - id, - } - } - /// Sends a message to the messager. pub async fn send(&self, message: Message) { trace!("Sending message via messager."); diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 62f309b09..34a62b140 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -18,18 +18,12 @@ use std::collections::VecDeque; use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; -use ethers::core::k256::sha2::digest::Mac; use futures_util::future::join_all; -use tokio::{spawn, task::JoinSet}; -use tracing::info; +use tokio::spawn; use self::{agent::AgentBuilder, machine::MachineInstruction}; use super::*; -use crate::{ - agent::Agent, - machine::{State, StateMachine}, - messager::Messager, -}; +use crate::{agent::Agent, machine::State, messager::Messager}; /// A world is a collection of agents that use the same type of provider, e.g., /// operate on the same blockchain or same `Environment`. The world is From 55425dce9fddba3f69b92335581f242d0d44e69a Mon Sep 17 00:00:00 2001 From: kinrezc Date: Fri, 2 Feb 2024 13:14:36 -0500 Subject: [PATCH 24/38] cleanup: improve agent docs, handle world.run() fallback behavior if no agents were added --- arbiter-engine/src/agent.rs | 23 ++--------------------- arbiter-engine/src/lib.rs | 1 + arbiter-engine/src/world.rs | 33 +++++++++------------------------ 3 files changed, 12 insertions(+), 45 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index d73d1e2c2..9b4af1abb 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -17,27 +17,8 @@ use thiserror::Error; /// dependency. /// /// # How it works -/// The [`Agent`] works by implementing the [`StateMachine`] trait. When the -/// [`World`] that owns the [`Agent`] is asked to enter into a new state, the -/// [`World`] will ask each [`Agent`] it owns to run that state transition by -/// calling [`StateMachine::run_state`]. All of the [`Agent`]s at once will then -/// will be able to be asked to block and wait to finish their state transition -/// by calling [`StateMachine::transition`]. Ultimately, the [`Agent`] will -/// transition through the following states: -/// 1. [`State::Uninitialized`]: The [`Agent`] has been created, but has not -/// been started. -/// 2. [`State::Syncing`]: The [`Agent`] is syncing with the world. This is -/// where the [`Agent`] can be brought up to date with the latest state of the -/// world. This could be used if the world was stopped and later restarted. -/// 3. [`State::Startup`]: The [`Agent`] is starting up. This is where the -/// [`Agent`] can be initialized and setup. -/// 4. [`State::Processing`]: The [`Agent`] is processing. This is where the -/// [`Agent`] can process events and produce actions. The [`State::Processing`] -/// stage may run for a long time before all [`Agent`]s are finished processing. -/// This is the main stage of the [`Agent`] that predominantly runs automation. -/// 5. [`State::Stopped`]: The [`Agent`] is stopped. This is where the [`Agent`] -/// can be stopped and state of the [`World`] and its [`Agent`]s can be -/// offloaded and saved. +/// When the [`World`] that owns the [`Agent`] is ran, it has each [`Agent`] run each of its [`Behavior`]s `startup()` methods. +/// The [`Behavior`]s themselves will return a stream of events that then let the [`Behavior`] move into the `State::Processing` stage. pub struct Agent { /// Identifier for this agent. /// Used for routing messages. diff --git a/arbiter-engine/src/lib.rs b/arbiter-engine/src/lib.rs index 1a2869242..52106e5a2 100644 --- a/arbiter-engine/src/lib.rs +++ b/arbiter-engine/src/lib.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; +use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; #[allow(unused)] use tracing::{debug, trace, warn}; diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 34a62b140..e1635e3e5 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -30,27 +30,9 @@ use crate::{agent::Agent, machine::State, messager::Messager}; /// responsible for managing the agents and their state transitions. /// /// # How it works -/// The [`World`] works by implementing the [`StateMachine`] trait. When the -/// [`World`] is asked to enter into a new state, it will ask each [`Agent`] it -/// owns to run that state transition by calling [`StateMachine::run_state`]. -/// All of the [`Agent`]s at once will then be able to be asked to block and -/// wait to finish their state transition by calling -/// [`StateMachine::transition`]. Ultimately, the [`World`] will transition -/// through the following states: -/// 1. [`State::Uninitialized`]: The [`World`] has been created, but has not -/// been started. -/// 2. [`State::Syncing`]: The [`World`] is syncing with the agents. This is -/// where the [`World`] can be brought up to date with the latest state of the -/// agents. This could be used if the world was stopped and later restarted. -/// 3. [`State::Startup`]: The [`World`] is starting up. This is where the -/// [`World`] can be initialized and setup. -/// 4. [`State::Processing`]: The [`World`] is processing. This is where the -/// [`World`] can process events and produce actions. The [`State::Processing`] -/// stage may run for a long time before all [`World`]s are finished processing. -/// This is the main stage of the [`World`] that predominantly runs automation. -/// 5. [`State::Stopped`]: The [`World`] is stopped. This is where the [`World`] -/// can be stopped and state of the [`World`] and its [`Agent`]s can be -/// offloaded and saved. +/// The [`World`] holds on to a collection of [`Agent`]s and can run them all concurrently when the +/// [`run`] method is called. The [`World`] takes in [`AgentBuilder`]s and when it does so, it creates [`Agent`]s that are now +/// connected to the world via a client ([`Arc`]) and a messager ([`Messager`]). pub struct World { /// The identifier of the world. pub id: String, @@ -91,10 +73,12 @@ impl World { } /// Runs the world through up to the [`State::Processing`] stage. - pub async fn run(&mut self) { + pub async fn run(&mut self) -> Result<()> { let mut tasks = vec![]; - // TODO: This unwrap should be checked. - let agents = self.agents.take().unwrap(); + let agents = self + .agents + .take() + .ok_or_else(|| anyhow!("No agents found! Has the world already been run?"))?; let mut messagers = VecDeque::new(); for (_, agent) in agents.iter() { for _ in &agent.behavior_engines { @@ -113,6 +97,7 @@ impl World { } } join_all(tasks).await; + Ok(()) } } From ba9c0edc4a2baed5cf50aba95852549ceea928e5 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Fri, 2 Feb 2024 13:25:03 -0500 Subject: [PATCH 25/38] fmt --- arbiter-engine/src/agent.rs | 17 +++++++++++------ .../examples/minter/behaviors/token_admin.rs | 1 - .../minter/behaviors/token_requester.rs | 5 +++-- arbiter-engine/src/examples/minter/mod.rs | 8 +++++--- .../src/examples/minter/token_minter.rs | 8 +++++--- arbiter-engine/src/messager.rs | 3 ++- arbiter-engine/src/world.rs | 8 +++++--- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 9b4af1abb..ad31e77a3 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -4,12 +4,12 @@ use std::{fmt::Debug, sync::Arc}; use arbiter_core::middleware::RevmMiddleware; use serde::de::DeserializeOwned; +use thiserror::Error; use crate::{ machine::{Behavior, Engine, State, StateMachine}, messager::Messager, }; -use thiserror::Error; /// An agent is an entity capable of processing events and producing actions. /// These are the core actors in simulations or in onchain systems. @@ -17,8 +17,10 @@ use thiserror::Error; /// dependency. /// /// # How it works -/// When the [`World`] that owns the [`Agent`] is ran, it has each [`Agent`] run each of its [`Behavior`]s `startup()` methods. -/// The [`Behavior`]s themselves will return a stream of events that then let the [`Behavior`] move into the `State::Processing` stage. +/// When the [`World`] that owns the [`Agent`] is ran, it has each [`Agent`] run +/// each of its [`Behavior`]s `startup()` methods. The [`Behavior`]s themselves +/// will return a stream of events that then let the [`Behavior`] move into the +/// `State::Processing` stage. pub struct Agent { /// Identifier for this agent. /// Used for routing messages. @@ -49,7 +51,8 @@ impl Agent { } } -/// [`AgentBuilder`] represents the intermediate state of agent creation before it is converted into a full on [`Agent`] +/// [`AgentBuilder`] represents the intermediate state of agent creation before +/// it is converted into a full on [`Agent`] pub struct AgentBuilder { /// Identifier for this agent. /// Used for routing messages. @@ -60,7 +63,8 @@ pub struct AgentBuilder { } impl AgentBuilder { - /// Appends a behavior onto an [`AgentBuilder`]. Behaviors are initialized when the agent builder is added to the [`crate::world::World`] + /// Appends a behavior onto an [`AgentBuilder`]. Behaviors are initialized + /// when the agent builder is added to the [`crate::world::World`] pub fn with_behavior( mut self, behavior: impl Behavior + 'static, @@ -96,7 +100,8 @@ impl AgentBuilder { /// enum representing the possible error states encountered by the agent builder #[derive(Debug, Error, Clone, PartialEq, Eq)] pub enum AgentBuildError { - /// Error representing the case where the agent is missing behavior engines; an agent has to have behaviors to be useful! + /// Error representing the case where the agent is missing behavior engines; + /// an agent has to have behaviors to be useful! #[error("Agent is missing behavior engines")] MissingBehaviorEngines, } diff --git a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs index b79a8f750..f7c09b37c 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs @@ -1,5 +1,4 @@ use self::examples::minter::agents::token_admin::TokenAdmin; - use super::*; /// Used as an action to ask what tokens are available. diff --git a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs index a460496dd..6a125485c 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs @@ -1,9 +1,10 @@ -use self::examples::minter::agents::token_requester::TokenRequester; -use super::*; use arbiter_bindings::bindings::arbiter_token::TransferFilter; use arbiter_core::data_collection::EventLogger; use token_admin::{MintRequest, TokenAdminQuery}; +use self::examples::minter::agents::token_requester::TokenRequester; +use super::*; + #[async_trait::async_trait] impl Behavior for TokenRequester { #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] diff --git a/arbiter-engine/src/examples/minter/mod.rs b/arbiter-engine/src/examples/minter/mod.rs index 861cd02d7..b4878aabb 100644 --- a/arbiter-engine/src/examples/minter/mod.rs +++ b/arbiter-engine/src/examples/minter/mod.rs @@ -3,15 +3,17 @@ pub mod agents; pub mod behaviors; pub mod token_minter; +use std::pin::Pin; + +use futures_util::Stream; +use tracing::error; + use crate::{ agent::Agent, machine::{Behavior, MachineHalt, MachineInstruction, StateMachine}, messager::To, world::World, }; -use futures_util::Stream; -use std::pin::Pin; -use tracing::error; const TOKEN_ADMIN_ID: &str = "token_admin"; const REQUESTER_ID: &str = "requester"; diff --git a/arbiter-engine/src/examples/minter/token_minter.rs b/arbiter-engine/src/examples/minter/token_minter.rs index 5e71b17af..e8319aeb2 100644 --- a/arbiter-engine/src/examples/minter/token_minter.rs +++ b/arbiter-engine/src/examples/minter/token_minter.rs @@ -1,11 +1,13 @@ -use super::*; -use crate::world::World; +use std::{str::FromStr, time::Duration}; + use agents::{token_admin::TokenAdmin, token_requester::TokenRequester}; use arbiter_core::data_collection::EventLogger; use ethers::types::Address; -use std::{str::FromStr, time::Duration}; use tokio::time::timeout; +use super::*; +use crate::world::World; + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn token_minter_simulation() { let mut world = World::new("test_world"); diff --git a/arbiter-engine/src/messager.rs b/arbiter-engine/src/messager.rs index cc13c7a90..15d95687d 100644 --- a/arbiter-engine/src/messager.rs +++ b/arbiter-engine/src/messager.rs @@ -76,7 +76,8 @@ impl Messager { } } - /// utility function for getting the next value from the broadcast_receiver without streaming + /// utility function for getting the next value from the broadcast_receiver + /// without streaming pub async fn get_next(&mut self) -> Message { while let Ok(message) = self.broadcast_receiver.as_mut().unwrap().recv().await { match &message.to { diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index e1635e3e5..005b5b616 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -30,9 +30,11 @@ use crate::{agent::Agent, machine::State, messager::Messager}; /// responsible for managing the agents and their state transitions. /// /// # How it works -/// The [`World`] holds on to a collection of [`Agent`]s and can run them all concurrently when the -/// [`run`] method is called. The [`World`] takes in [`AgentBuilder`]s and when it does so, it creates [`Agent`]s that are now -/// connected to the world via a client ([`Arc`]) and a messager ([`Messager`]). +/// The [`World`] holds on to a collection of [`Agent`]s and can run them all +/// concurrently when the [`run`] method is called. The [`World`] takes in +/// [`AgentBuilder`]s and when it does so, it creates [`Agent`]s that are now +/// connected to the world via a client ([`Arc`]) and a messager +/// ([`Messager`]). pub struct World { /// The identifier of the world. pub id: String, From 01bb6eaa9fd3abee5772ab97d5bce7baa9f217cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 18:26:12 +0000 Subject: [PATCH 26/38] build(deps): bump config from 0.13.4 to 0.14.0 Bumps [config](https://github.com/mehcode/config-rs) from 0.13.4 to 0.14.0. - [Changelog](https://github.com/mehcode/config-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/mehcode/config-rs/compare/v0.13.4...0.14.0) --- updated-dependencies: - dependency-name: config dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 123 +++++++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- 2 files changed, 68 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3499b5cd8..83cd6a6a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,17 +38,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "ahash" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.7" @@ -1088,11 +1077,12 @@ dependencies = [ [[package]] name = "config" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" dependencies = [ "async-trait", + "convert_case 0.6.0", "json5", "lazy_static", "nom", @@ -1101,7 +1091,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml 0.5.11", + "toml 0.8.9", "yaml-rust", ] @@ -1124,6 +1114,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-random" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1136,6 +1146,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1314,7 +1333,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.0", @@ -1398,9 +1417,12 @@ dependencies = [ [[package]] name = "dlv-list" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] [[package]] name = "doc-comment" @@ -2279,22 +2301,13 @@ dependencies = [ "serde", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.7", -] - [[package]] name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.7", + "ahash", ] [[package]] @@ -2303,7 +2316,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.7", + "ahash", "allocator-api2", "rayon", "serde", @@ -3190,12 +3203,12 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-multimap" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" dependencies = [ "dlv-list", - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -3553,7 +3566,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce68a02f698ff7787c261aea1b4c040a8fe183a8fb200e2436d7f35d95a1b86f" dependencies = [ - "ahash 0.8.7", + "ahash", "arrow-format", "atoi_simd", "bytemuck", @@ -3599,7 +3612,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0f5efe734b6cbe5f97ea769be8360df5324fade396f1f3f5ad7fe9360ca4a23" dependencies = [ - "ahash 0.8.7", + "ahash", "bitflags 2.4.2", "bytemuck", "chrono", @@ -3642,7 +3655,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d0458efe8946f4718fd352f230c0db5a37926bd0d2bd25af79dc24746abaaea" dependencies = [ - "ahash 0.8.7", + "ahash", "async-trait", "atoi_simd", "bytes", @@ -3680,7 +3693,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea47d46b7a98fa683ef235ad48b783abf61734828e754096cfbdc77404fff9b3" dependencies = [ - "ahash 0.8.7", + "ahash", "chrono", "fallible-streaming-iterator", "hashbrown 0.14.3", @@ -3701,7 +3714,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d7105b40905bb38e8fc4a7fd736594b7491baa12fad3ac492969ca221a1b5d5" dependencies = [ - "ahash 0.8.7", + "ahash", "bitflags 2.4.2", "glob", "once_cell", @@ -3725,7 +3738,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e09afc456ab11e75e5dcb43e00a01c71f3a46a2781e450054acb6bb096ca78e" dependencies = [ - "ahash 0.8.7", + "ahash", "argminmax", "bytemuck", "either", @@ -3750,7 +3763,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba24d67b1f64ab85143033dd46fa090b13c0f74acdf91b0780c16aecf005e3d" dependencies = [ - "ahash 0.8.7", + "ahash", "async-stream", "base64 0.21.7", "brotli", @@ -3800,7 +3813,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384a175624d050c31c473ee11df9d7af5d729ae626375e522158cfb3d150acd0" dependencies = [ - "ahash 0.8.7", + "ahash", "bytemuck", "once_cell", "percent-encoding", @@ -3872,7 +3885,7 @@ version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b174ca4a77ad47d7b91a0460aaae65bbf874c8bfbaaa5308675dadef3976bbda" dependencies = [ - "ahash 0.8.7", + "ahash", "bytemuck", "hashbrown 0.14.3", "indexmap", @@ -4410,13 +4423,14 @@ dependencies = [ [[package]] name = "ron" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", + "base64 0.21.7", + "bitflags 2.4.2", "serde", + "serde_derive", ] [[package]] @@ -4451,9 +4465,9 @@ checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" [[package]] name = "rust-ini" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" dependencies = [ "cfg-if", "ordered-multimap", @@ -4877,7 +4891,7 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2faf8f101b9bc484337a6a6b0409cf76c139f2fb70a9e3aee6b6774be7bfbf76" dependencies = [ - "ahash 0.8.7", + "ahash", "getrandom", "halfbrown", "lexical-core", @@ -5448,15 +5462,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.7.8" @@ -5723,6 +5728,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index df9066ba1..db8bfb07e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ arbiter-core.workspace = true clap = { version = "=4.4.18", features = ["derive"] } serde.workspace = true serde_json.workspace = true -config = { version = "=0.13.4" } +config = { version = "=0.14.0" } ethers.workspace = true revm.workspace = true toml = { version = "=0.8.9" } From 4b9d59e5c4dcd3d19eb1d662d8eb53a90929cb4f Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Fri, 2 Feb 2024 11:53:53 -0700 Subject: [PATCH 27/38] fix: post merge issues, good to go --- arbiter-engine/src/agent.rs | 14 ++++------- .../src/examples/minter/agents/mod.rs | 4 ++-- .../src/examples/minter/agents/token_admin.rs | 2 +- .../examples/minter/agents/token_requester.rs | 2 +- .../src/examples/minter/behaviors/mod.rs | 4 ++-- arbiter-engine/src/examples/minter/mod.rs | 8 +++---- .../src/examples/minter/token_minter.rs | 4 ++-- arbiter-engine/src/examples/mod.rs | 10 ++------ arbiter-engine/src/examples/timed_message.rs | 11 +++++---- arbiter-engine/src/machine.rs | 21 ++++++++++++---- arbiter-engine/src/universe.rs | 24 ++++++++++++------- arbiter-engine/src/world.rs | 12 ++++------ 12 files changed, 61 insertions(+), 55 deletions(-) diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 5eba5bfee..7ad29c93c 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -7,7 +7,7 @@ use serde::de::DeserializeOwned; use thiserror::Error; use crate::{ - machine::{Behavior, Engine, State, StateMachine}, + machine::{Behavior, Engine, StateMachine}, messager::Messager, }; @@ -26,9 +26,6 @@ pub struct Agent { /// Used for routing messages. pub id: String, - /// The status of the agent. - pub state: State, - /// The messager the agent uses to send and receive messages from other /// agents. pub messager: Messager, @@ -45,10 +42,8 @@ impl Debug for Agent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Agent") .field("id", &self.id) - .field("state", &self.state) .field("messager", &self.messager) .field("client", &self.client) - .field("event_streamer", &self.event_streamer) .field("behavior_engines", &self.behavior_engines) .finish() } @@ -56,11 +51,11 @@ impl Debug for Agent { impl Agent { /// Produces a minimal agent builder with the given identifier. - pub fn builder(id: &str) -> Result { - Ok(AgentBuilder { + pub fn builder(id: &str) -> AgentBuilder { + AgentBuilder { id: id.to_owned(), behavior_engines: None, - }) + } } } @@ -100,7 +95,6 @@ impl AgentBuilder { match self.behavior_engines { Some(engines) => Ok(Agent { id: self.id, - state: State::Uninitialized, messager, client, behavior_engines: engines, diff --git a/arbiter-engine/src/examples/minter/agents/mod.rs b/arbiter-engine/src/examples/minter/agents/mod.rs index 7db494eed..2998593ee 100644 --- a/arbiter-engine/src/examples/minter/agents/mod.rs +++ b/arbiter-engine/src/examples/minter/agents/mod.rs @@ -1,3 +1,3 @@ use super::*; -pub mod token_admin; -pub mod token_requester; +pub(crate) mod token_admin; +pub(crate) mod token_requester; diff --git a/arbiter-engine/src/examples/minter/agents/token_admin.rs b/arbiter-engine/src/examples/minter/agents/token_admin.rs index f3ef512cf..300ae397c 100644 --- a/arbiter-engine/src/examples/minter/agents/token_admin.rs +++ b/arbiter-engine/src/examples/minter/agents/token_admin.rs @@ -1,7 +1,7 @@ use super::*; #[derive(Debug)] -pub struct TokenAdmin { +pub(crate) struct TokenAdmin { /// The identifier of the token admin. pub token_data: HashMap, pub tokens: Option>>, diff --git a/arbiter-engine/src/examples/minter/agents/token_requester.rs b/arbiter-engine/src/examples/minter/agents/token_requester.rs index 9fa40d9fe..10454717c 100644 --- a/arbiter-engine/src/examples/minter/agents/token_requester.rs +++ b/arbiter-engine/src/examples/minter/agents/token_requester.rs @@ -3,7 +3,7 @@ use super::*; /// The token requester is responsible for requesting tokens from the token /// admin. This agents is purely for testing purposes as far as I can tell. #[derive(Debug)] -pub struct TokenRequester { +pub(crate) struct TokenRequester { /// The tokens that the token requester has requested. pub token_data: TokenData, /// The agent ID to request tokens to. diff --git a/arbiter-engine/src/examples/minter/behaviors/mod.rs b/arbiter-engine/src/examples/minter/behaviors/mod.rs index 7db494eed..2998593ee 100644 --- a/arbiter-engine/src/examples/minter/behaviors/mod.rs +++ b/arbiter-engine/src/examples/minter/behaviors/mod.rs @@ -1,3 +1,3 @@ use super::*; -pub mod token_admin; -pub mod token_requester; +pub(crate) mod token_admin; +pub(crate) mod token_requester; diff --git a/arbiter-engine/src/examples/minter/mod.rs b/arbiter-engine/src/examples/minter/mod.rs index b4878aabb..428b05123 100644 --- a/arbiter-engine/src/examples/minter/mod.rs +++ b/arbiter-engine/src/examples/minter/mod.rs @@ -1,7 +1,7 @@ use super::*; -pub mod agents; -pub mod behaviors; -pub mod token_minter; +pub(crate) mod agents; +pub(crate) mod behaviors; +pub(crate) mod token_minter; use std::pin::Pin; @@ -22,7 +22,7 @@ const TOKEN_SYMBOL: &str = "ARB"; const TOKEN_DECIMALS: u8 = 18; #[derive(Clone, Debug, Deserialize, Serialize)] -pub struct TokenData { +pub(crate) struct TokenData { pub name: String, pub symbol: String, pub decimals: u8, diff --git a/arbiter-engine/src/examples/minter/token_minter.rs b/arbiter-engine/src/examples/minter/token_minter.rs index e8319aeb2..d1cd93c9a 100644 --- a/arbiter-engine/src/examples/minter/token_minter.rs +++ b/arbiter-engine/src/examples/minter/token_minter.rs @@ -14,7 +14,7 @@ async fn token_minter_simulation() { let client = RevmMiddleware::new(&world.environment, None).unwrap(); // Create the token admin agent - let token_admin = Agent::builder(TOKEN_ADMIN_ID).unwrap(); + let token_admin = Agent::builder(TOKEN_ADMIN_ID); let mut token_admin_behavior = TokenAdmin::new(Some(4)); token_admin_behavior.add_token(TokenData { name: TOKEN_NAME.to_owned(), @@ -23,7 +23,7 @@ async fn token_minter_simulation() { address: None, }); // Create the token requester agent - let token_requester = Agent::builder(REQUESTER_ID).unwrap(); + let token_requester = Agent::builder(REQUESTER_ID); let mut token_requester_behavior = TokenRequester::new(Some(4)); world.add_agent(token_requester.with_behavior(token_requester_behavior)); diff --git a/arbiter-engine/src/examples/mod.rs b/arbiter-engine/src/examples/mod.rs index cb7653b28..07162b385 100644 --- a/arbiter-engine/src/examples/mod.rs +++ b/arbiter-engine/src/examples/mod.rs @@ -1,11 +1,5 @@ #![warn(missing_docs)] #![allow(unused)] -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// TODO: Notes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Create a BlockAdmin and a TokenAdmin. -// Potentially create an `Orchestrator`` that sends instructions to both -// BlockAdmin and TokenAdmin. -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //! The examples module contains example strategies. @@ -18,5 +12,5 @@ use futures_util::{stream, StreamExt}; use super::*; use crate::messager::{Message, Messager}; -mod minter; -mod timed_message; +pub(crate) mod minter; +pub(crate) mod timed_message; diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message.rs index 56fe2be65..383b59c7f 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message.rs @@ -17,7 +17,8 @@ use crate::{ world::World, }; -struct TimedMessage { +#[derive(Debug)] +pub(crate) struct TimedMessage { delay: u64, receive_data: String, send_data: String, @@ -99,7 +100,7 @@ impl Behavior for TimedMessage { async fn echoer() { let mut world = World::new("world"); - let agent = Agent::builder(AGENT_ID).unwrap(); + let agent = Agent::builder(AGENT_ID); let behavior = TimedMessage::new( 1, "Hello, world!".to_owned(), @@ -135,7 +136,7 @@ async fn echoer() { async fn ping_pong() { let mut world = World::new("world"); - let agent = Agent::builder(AGENT_ID).unwrap(); + let agent = Agent::builder(AGENT_ID); let behavior_ping = TimedMessage::new( 1, "pong".to_owned(), @@ -176,8 +177,8 @@ async fn ping_pong() { async fn ping_pong_two_agent() { let mut world = World::new("world"); - let agent_ping = Agent::builder("agent_ping").unwrap(); - let agent_pong = Agent::builder("agent_pong").unwrap(); + let agent_ping = Agent::builder("agent_ping"); + let agent_pong = Agent::builder("agent_pong"); let behavior_ping = TimedMessage::new( 1, diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index c7229a334..c0c246ac7 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -53,7 +53,7 @@ pub enum State { /// The [`Behavior`] trait is the lowest level functionality that will be used /// by a [`StateMachine`]. This constitutes what each state transition will do. #[async_trait::async_trait] -pub trait Behavior: Send + Sync + 'static { +pub trait Behavior: Send + Sync + Debug + 'static { /// Used to start the agent. /// This is where the agent can engage in its specific start up activities /// that it can do given the current state of the world. @@ -70,7 +70,7 @@ pub trait Behavior: Send + Sync + 'static { } #[async_trait::async_trait] -pub(crate) trait StateMachine: Send + Sync + 'static { +pub(crate) trait StateMachine: Send + Sync + Debug + 'static { async fn execute(&mut self, instruction: MachineInstruction); } @@ -100,10 +100,23 @@ where phantom: std::marker::PhantomData, } -impl Engine +impl Debug for Engine where B: Behavior, E: DeserializeOwned + Send + Sync + 'static, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Engine") + .field("behavior", &self.behavior) + .field("state", &self.state) + .finish() + } +} + +impl Engine +where + B: Behavior + Debug, + E: DeserializeOwned + Send + Sync + 'static, { /// Creates a new [`Engine`] with the given [`Behavior`] and [`Receiver`]. pub(crate) fn new(behavior: B) -> Self { @@ -119,7 +132,7 @@ where #[async_trait::async_trait] impl StateMachine for Engine where - B: Behavior, + B: Behavior + Debug, E: DeserializeOwned + Send + Sync + Debug + 'static, { async fn execute(&mut self, instruction: MachineInstruction) { diff --git a/arbiter-engine/src/universe.rs b/arbiter-engine/src/universe.rs index ca77fb0ed..66154dff6 100644 --- a/arbiter-engine/src/universe.rs +++ b/arbiter-engine/src/universe.rs @@ -41,8 +41,12 @@ impl Universe { return Err(anyhow::anyhow!("Universe is already running.")); } let mut tasks = Vec::new(); - for (_, world) in self.worlds.take().unwrap().drain() { - tasks.push(spawn(async move { world.run().await })); + // TODO: These unwraps need to be checkdd a bit. + for (_, mut world) in self.worlds.take().unwrap().drain() { + tasks.push(spawn(async move { + world.run().await.unwrap(); + world + })); } self.world_tasks = Some(join_all(tasks.into_iter()).await); Ok(()) @@ -61,7 +65,7 @@ mod tests { use tracing_subscriber::{fmt, EnvFilter}; use super::*; - use crate::{agent::Agent, examples::timed_message::*, machine::State}; + use crate::{agent::Agent, examples::timed_message::*}; #[tokio::test] async fn run_universe() { @@ -69,8 +73,7 @@ mod tests { let world = World::new("test"); universe.add_world(world); universe.run_worlds().await.unwrap(); - let world = universe.world_tasks.unwrap().remove(0).unwrap(); - assert_eq!(world.state, State::Processing); + universe.world_tasks.unwrap().remove(0).unwrap(); } #[tokio::test] @@ -97,7 +100,7 @@ mod tests { .expect("setting default subscriber failed"); let mut world1 = World::new("test1"); - let agent1 = Agent::new("agent1", &world1); + let agent1 = Agent::builder("agent1"); let behavior1 = TimedMessage::new( 1, "echo".to_owned(), @@ -108,7 +111,7 @@ mod tests { world1.add_agent(agent1.with_behavior(behavior1)); let mut world2 = World::new("test2"); - let agent2 = Agent::new("agent2", &world2); + let agent2 = Agent::builder("agent2"); let behavior2 = TimedMessage::new( 1, "echo".to_owned(), @@ -127,14 +130,17 @@ mod tests { let parsed_file = read_to_string("test_logs_engine.log").expect("Unable to read log file"); // Define the line to check (excluding the timestamp) - let line_to_check = "World is syncing."; + let line_to_check = "Behavior is starting up."; // Assert that the lines appear consecutively assert!( lines_appear_consecutively(&parsed_file, line_to_check), "The lines do not appear consecutively" ); - remove_file("test_logs_engine.log").expect("Unable to remove log file"); + remove_file("test_logs_engine.log").expect( + "Unable to remove log + file", + ); } fn lines_appear_consecutively(file_contents: &str, line_to_check: &str) -> bool { diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 005b5b616..0cadd1cca 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -23,7 +23,7 @@ use tokio::spawn; use self::{agent::AgentBuilder, machine::MachineInstruction}; use super::*; -use crate::{agent::Agent, machine::State, messager::Messager}; +use crate::{agent::Agent, messager::Messager}; /// A world is a collection of agents that use the same type of provider, e.g., /// operate on the same blockchain or same `Environment`. The world is @@ -35,13 +35,11 @@ use crate::{agent::Agent, machine::State, messager::Messager}; /// [`AgentBuilder`]s and when it does so, it creates [`Agent`]s that are now /// connected to the world via a client ([`Arc`]) and a messager /// ([`Messager`]). +#[derive(Debug)] pub struct World { /// The identifier of the world. pub id: String, - /// The state of the [`World`]. - pub state: State, - /// The agents in the world. pub agents: Option>, @@ -53,11 +51,10 @@ pub struct World { } impl World { - /// Creates a new [World] with the given identifier and provider. + /// Creates a new [`World`] with the given identifier and provider. pub fn new(id: &str) -> Self { Self { id: id.to_owned(), - state: State::Uninitialized, agents: Some(HashMap::new()), environment: Environment::builder().build(), messager: Messager::new(), @@ -74,7 +71,8 @@ impl World { agents.insert(id.to_owned(), agent); } - /// Runs the world through up to the [`State::Processing`] stage. + /// Runs all of the [`Agent`]s and their [`crate::machine::Behavior`]s in + /// the world in parallel. pub async fn run(&mut self) -> Result<()> { let mut tasks = vec![]; let agents = self From d0ec29a5fdfc76940ee9db7657aa6d975f5b85b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 20:08:25 +0000 Subject: [PATCH 28/38] build(deps): bump tokio from 1.35.1 to 1.36.0 Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.35.1 to 1.36.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.35.1...tokio-1.36.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83cd6a6a1..a0cf7f3c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5384,9 +5384,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", diff --git a/Cargo.toml b/Cargo.toml index db8bfb07e..075c14dae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ thiserror = { version = "1.0.55" } syn = { version = "2.0.43" } quote = { version = "=1.0.33" } proc-macro2 = { version = "1.0.78" } -tokio = { version = "1.35.1", features = ["macros", "full"] } +tokio = { version = "1.36.0", features = ["macros", "full"] } crossbeam-channel = { version = "0.5.11" } futures-util = { version = "=0.3.30" } async-trait = { version = "0.1.76" } From 28f7ab35e08d89a5e16f64fb34c24061ff5bdc28 Mon Sep 17 00:00:00 2001 From: Matt Czernik Date: Mon, 5 Feb 2024 18:20:52 -0500 Subject: [PATCH 29/38] Simulation Configs (#843) - Feat(arbiter-engine): config based simulation workflows (#825) Co-authored-by: Colin Roberts --- Cargo.lock | 83 ++++++----- Cargo.toml | 38 +++-- arbiter-engine/Cargo.toml | 11 +- arbiter-engine/src/agent.rs | 84 ++++++++++- .../src/examples/minter/agents/mod.rs | 4 + .../src/examples/minter/agents/token_admin.rs | 9 +- .../examples/minter/agents/token_requester.rs | 6 +- .../examples/minter/behaviors/token_admin.rs | 13 +- .../minter/behaviors/token_requester.rs | 50 +++---- .../src/examples/minter/config.toml | 10 ++ arbiter-engine/src/examples/minter/mod.rs | 75 +++++++++- .../src/examples/minter/token_minter.rs | 60 -------- arbiter-engine/src/examples/mod.rs | 13 +- .../src/examples/timed_message/config.toml | 5 + .../mod.rs} | 73 +++++---- arbiter-engine/src/machine.rs | 108 +++++++++++--- arbiter-engine/src/messager.rs | 37 ++++- arbiter-engine/src/world.rs | 138 +++++++++++++++++- arbiter-macros/Cargo.toml | 10 ++ arbiter-macros/src/lib.rs | 49 +++++++ 20 files changed, 635 insertions(+), 241 deletions(-) create mode 100644 arbiter-engine/src/examples/minter/config.toml delete mode 100644 arbiter-engine/src/examples/minter/token_minter.rs create mode 100644 arbiter-engine/src/examples/timed_message/config.toml rename arbiter-engine/src/examples/{timed_message.rs => timed_message/mod.rs} (77%) create mode 100644 arbiter-macros/Cargo.toml create mode 100644 arbiter-macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a0cf7f3c1..0d78808a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,7 +138,7 @@ checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -231,13 +231,12 @@ dependencies = [ "ethers", "foundry-config", "proc-macro2", - "quote", "rayon", "revm", "revm-primitives 1.3.0 (git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858)", "serde", "serde_json", - "syn 2.0.43", + "syn 2.0.48", "tempfile", "thiserror", "tokio", @@ -295,6 +294,7 @@ dependencies = [ "anyhow", "arbiter-bindings", "arbiter-core", + "arbiter-macros", "async-stream", "async-trait", "crossbeam-channel", @@ -306,11 +306,20 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", + "toml 0.8.9", "tracing", "tracing-subscriber", "tracing-test", ] +[[package]] +name = "arbiter-macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.48", +] + [[package]] name = "argminmax" version = "0.6.1" @@ -515,7 +524,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -526,7 +535,7 @@ checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -582,7 +591,7 @@ checksum = "823b8bb275161044e2ac7a25879cb3e2480cb403e3943022c7c769c599b756aa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -655,7 +664,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.43", + "syn 2.0.48", "which", ] @@ -806,7 +815,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -996,7 +1005,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1536,7 +1545,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1547,7 +1556,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1720,7 +1729,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.43", + "syn 2.0.48", "toml 0.8.9", "walkdir", ] @@ -1738,7 +1747,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1764,7 +1773,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.43", + "syn 2.0.48", "tempfile", "thiserror", "tiny-keccak", @@ -2148,7 +2157,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3146,7 +3155,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3347,7 +3356,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3402,7 +3411,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3466,7 +3475,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3504,7 +3513,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3950,7 +3959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -4012,7 +4021,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "version_check", "yansi 1.0.0-rc.1", ] @@ -4056,9 +4065,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -4181,7 +4190,7 @@ checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -4750,7 +4759,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5107,7 +5116,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5175,9 +5184,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -5285,7 +5294,7 @@ checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5305,7 +5314,7 @@ checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5409,7 +5418,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5558,7 +5567,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5870,7 +5879,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -5904,7 +5913,7 @@ checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6226,7 +6235,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -6246,7 +6255,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 075c14dae..77b89dcbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,12 @@ [workspace] # List of crates included in this workspace -members = [ "arbiter-bindings", "arbiter-core", "arbiter-engine", "documentation"] +members = [ + "arbiter-bindings", + "arbiter-core", + "arbiter-engine", + "arbiter-macros", + "documentation", +] # List of crates excluded from this workspace exclude = ["benches"] @@ -10,7 +16,10 @@ exclude = ["benches"] name = "arbiter" version = "0.4.13" edition = "2021" -authors = ["Waylon Jepsen ", "Colin Roberts "] +authors = [ + "Waylon Jepsen ", + "Colin Roberts ", +] description = "Allowing smart contract developers to do simulation driven development via an EVM emulator" license = "Apache-2.0" keywords = ["ethereum", "evm", "emulator", "testing", "smart-contracts"] @@ -23,21 +32,27 @@ path = "bin/main.rs" [workspace.dependencies] arbiter-bindings = { version = "*", path = "./arbiter-bindings" } arbiter-core = { version = "*", path = "./arbiter-core" } +arbiter-macros = { path = "./arbiter-macros" } +arbiter-engine = { path = "./arbiter-engine" } ethers = { version = "2.0.13" } serde = { version = "1.0.193", features = ["derive"] } serde_json = { version = "=1.0.108" } -revm = { git = "https://github.com/bluealloy/revm.git", features = [ "ethersdb", "std", "serde"], rev = "30bbcdfe81446c9d1e9b37acc95f208943ddf858" } +revm = { git = "https://github.com/bluealloy/revm.git", features = [ + "ethersdb", + "std", + "serde", +], rev = "30bbcdfe81446c9d1e9b37acc95f208943ddf858" } revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "30bbcdfe81446c9d1e9b37acc95f208943ddf858" } thiserror = { version = "1.0.55" } -syn = { version = "2.0.43" } -quote = { version = "=1.0.33" } +syn = { version = "2.0.48", features = ["full"] } proc-macro2 = { version = "1.0.78" } tokio = { version = "1.36.0", features = ["macros", "full"] } -crossbeam-channel = { version = "0.5.11" } -futures-util = { version = "=0.3.30" } -async-trait = { version = "0.1.76" } +crossbeam-channel = { version = "0.5.11" } +futures-util = { version = "=0.3.30" } +async-trait = { version = "0.1.76" } tracing = "0.1.40" async-stream = "0.3.5" +toml = { version = "=0.8.9" } # Dependencies for the release build [dependencies] @@ -50,15 +65,14 @@ serde_json.workspace = true config = { version = "=0.14.0" } ethers.workspace = true revm.workspace = true -toml = { version = "=0.8.9" } +toml.workspace = true proc-macro2.workspace = true syn.workspace = true Inflector = { version = "=0.11.4" } # Building files -quote.workspace = true foundry-config = { version = "=0.2.0" } -tempfile = { version = "3.9.0"} +tempfile = { version = "3.9.0" } # Errors thiserror.workspace = true @@ -76,5 +90,3 @@ lto = true # The Rust compiler splits your crate into multiple codegen units to parallelize (and thus speed up) compilation but at the cost of optimization. # This setting tells the compiler to use only one codegen unit, which will slow down compilation but improve optimization. codegen-units = 1 - - diff --git a/arbiter-engine/Cargo.toml b/arbiter-engine/Cargo.toml index 57d14be30..0059563ad 100644 --- a/arbiter-engine/Cargo.toml +++ b/arbiter-engine/Cargo.toml @@ -2,7 +2,10 @@ name = "arbiter-engine" version = "0.1.0" edition = "2021" -authors = ["Waylon Jepsen ", "Colin Roberts "] +authors = [ + "Waylon Jepsen ", + "Colin Roberts ", +] description = "Allowing smart contract developers to do simulation driven development via an EVM emulator" license = "Apache-2.0" keywords = ["ethereum", "evm", "emulator", "testing", "smart-contracts"] @@ -12,6 +15,7 @@ homepage = "https://github.com/primitivefinance/arbiter" repository = "https://github.com/primitivefinance/arbiter" [dependencies] +arbiter-macros.workspace = true ethers.workspace = true futures-util.workspace = true async-trait.workspace = true @@ -19,14 +23,15 @@ serde_json.workspace = true serde.workspace = true tokio.workspace = true async-stream.workspace = true -anyhow = { version = "=1.0.79" } +anyhow = { version = "=1.0.79" } tracing.workspace = true -tokio-stream = "0.1.14" +tokio-stream = "0.1.14" futures = "0.3.30" crossbeam-channel.workspace = true arbiter-core.workspace = true arbiter-bindings.workspace = true thiserror.workspace = true +toml.workspace = true [dev-dependencies] arbiter-core.workspace = true diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 7ad29c93c..d7330a488 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -3,7 +3,7 @@ use std::{fmt::Debug, sync::Arc}; use arbiter_core::middleware::RevmMiddleware; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; use crate::{ @@ -50,7 +50,22 @@ impl Debug for Agent { } impl Agent { - /// Produces a minimal agent builder with the given identifier. + /// Creates a new [`AgentBuilder`] instance with a specified identifier. + /// + /// This method initializes an [`AgentBuilder`] with the provided `id` and + /// sets the `behavior_engines` field to `None`. The returned + /// [`AgentBuilder`] can be further configured using its methods before + /// finalizing the creation of an [`Agent`]. + /// + /// # Arguments + /// + /// * `id` - A string slice that holds the identifier for the agent being + /// built. + /// + /// # Returns + /// + /// Returns an [`AgentBuilder`] instance that can be used to configure and + /// build an [`Agent`]. pub fn builder(id: &str) -> AgentBuilder { AgentBuilder { id: id.to_owned(), @@ -73,9 +88,9 @@ pub struct AgentBuilder { impl AgentBuilder { /// Appends a behavior onto an [`AgentBuilder`]. Behaviors are initialized /// when the agent builder is added to the [`crate::world::World`] - pub fn with_behavior( + pub fn with_behavior( mut self, - behavior: impl Behavior + 'static, + behavior: impl Behavior + Serialize + DeserializeOwned + 'static, ) -> Self { let engine = Engine::new(behavior); if let Some(engines) = &mut self.behavior_engines { @@ -86,7 +101,66 @@ impl AgentBuilder { self } - /// Produces a new [`Agent`] with the given identifier. + /// Adds a state machine engine to the agent builder. + /// + /// This method allows for the addition of a custom state machine engine to + /// the agent's behavior engines. If the agent builder already has some + /// engines, the new engine is appended to the list. If no engines are + /// present, a new list is created with the provided engine as its first + /// element. + /// + /// # Parameters + /// + /// - `engine`: The state machine engine to be added to the agent builder. + /// This engine must + /// implement the `StateMachine` trait and is expected to be provided as a + /// boxed trait object to allow for dynamic dispatch. + /// + /// # Returns + /// + /// Returns the `AgentBuilder` instance to allow for method chaining. + pub fn with_engine(mut self, engine: Box) -> Self { + if let Some(engines) = &mut self.behavior_engines { + engines.push(engine); + } else { + self.behavior_engines = Some(vec![engine]); + }; + self + } + + /// Constructs and returns a new [`Agent`] instance using the provided + /// `client` and `messager`. + /// + /// This method finalizes the building process of an [`Agent`] by taking + /// ownership of the builder, and attempting to construct an `Agent` + /// with the accumulated configurations and the provided `client` and + /// `messager`. The `client` is an [`Arc`] that represents + /// the connection to the blockchain or environment, and `messager` is a + /// communication layer for the agent. + /// + /// # Parameters + /// + /// - `client`: A shared [`Arc`] instance that provides the + /// agent with access to the blockchain or environment. + /// - `messager`: A [`Messager`] instance for the agent to communicate with + /// other agents or systems. + /// + /// # Returns + /// + /// Returns a `Result` that, on success, contains the newly created + /// [`Agent`] instance. On failure, it returns an + /// [`AgentBuildError::MissingBehaviorEngines`] error indicating that the + /// agent was attempted to be built without any behavior engines + /// configured. + /// + /// # Examples + /// + /// ```ignore + /// let agent_builder = AgentBuilder::new("agent_id"); + /// let client = Arc::new(RevmMiddleware::new(...)); + /// let messager = Messager::new(...); + /// let agent = agent_builder.build(client, messager).expect("Failed to build agent"); + /// ``` pub fn build( self, client: Arc, diff --git a/arbiter-engine/src/examples/minter/agents/mod.rs b/arbiter-engine/src/examples/minter/agents/mod.rs index 2998593ee..7311e5619 100644 --- a/arbiter-engine/src/examples/minter/agents/mod.rs +++ b/arbiter-engine/src/examples/minter/agents/mod.rs @@ -1,3 +1,7 @@ use super::*; pub(crate) mod token_admin; pub(crate) mod token_requester; + +pub fn default_max_count() -> Option { + Some(5) +} diff --git a/arbiter-engine/src/examples/minter/agents/token_admin.rs b/arbiter-engine/src/examples/minter/agents/token_admin.rs index 300ae397c..6507accf5 100644 --- a/arbiter-engine/src/examples/minter/agents/token_admin.rs +++ b/arbiter-engine/src/examples/minter/agents/token_admin.rs @@ -1,15 +1,19 @@ use super::*; -#[derive(Debug)] +#[derive(Deserialize, Serialize, Clone, Debug)] pub(crate) struct TokenAdmin { /// The identifier of the token admin. pub token_data: HashMap, + #[serde(skip)] pub tokens: Option>>, + #[serde(skip)] pub client: Option>, + #[serde(skip)] pub messager: Option, + #[serde(default)] pub count: u64, + #[serde(default = "default_max_count")] pub max_count: Option, - startup_message: Option, } impl TokenAdmin { @@ -21,7 +25,6 @@ impl TokenAdmin { messager: None, count: 0, max_count, - startup_message: None, } } diff --git a/arbiter-engine/src/examples/minter/agents/token_requester.rs b/arbiter-engine/src/examples/minter/agents/token_requester.rs index 10454717c..5a3229e30 100644 --- a/arbiter-engine/src/examples/minter/agents/token_requester.rs +++ b/arbiter-engine/src/examples/minter/agents/token_requester.rs @@ -2,17 +2,21 @@ use super::*; /// The token requester is responsible for requesting tokens from the token /// admin. This agents is purely for testing purposes as far as I can tell. -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub(crate) struct TokenRequester { /// The tokens that the token requester has requested. pub token_data: TokenData, /// The agent ID to request tokens to. pub request_to: String, /// Client to have an address to receive token mint to and check balance + #[serde(skip)] pub client: Option>, /// The messaging layer for the token requester. + #[serde(skip)] pub messager: Option, + #[serde(default)] pub count: u64, + #[serde(default = "default_max_count")] pub max_count: Option, } diff --git a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs index f7c09b37c..59d022712 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs @@ -1,4 +1,4 @@ -use self::examples::minter::agents::token_admin::TokenAdmin; +use self::{examples::minter::agents::token_admin::TokenAdmin, machine::EventStream}; use super::*; /// Used as an action to ask what tokens are available. @@ -31,7 +31,7 @@ impl Behavior for TokenAdmin { &mut self, client: Arc, messager: Messager, - ) -> Pin + Send + Sync>> { + ) -> EventStream { self.messager = Some(messager.clone()); self.client = Some(client.clone()); for token_data in self.token_data.values_mut() { @@ -76,12 +76,9 @@ impl Behavior for TokenAdmin { token_name.clone() ); let token_data = self.token_data.get(&token_name).unwrap(); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(event.from.clone()), // Reply back to sender - data: serde_json::to_string(&token_data.address).unwrap(), - }; - messager.send(message).await; + messager + .send(To::Agent(event.from.clone()), token_data.address) + .await; } TokenAdminQuery::MintRequest(mint_request) => { trace!("Minting tokens: {:?}", mint_request); diff --git a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs index 6a125485c..c662d3969 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs @@ -2,7 +2,7 @@ use arbiter_bindings::bindings::arbiter_token::TransferFilter; use arbiter_core::data_collection::EventLogger; use token_admin::{MintRequest, TokenAdminQuery}; -use self::examples::minter::agents::token_requester::TokenRequester; +use self::{examples::minter::agents::token_requester::TokenRequester, machine::EventStream}; use super::*; #[async_trait::async_trait] @@ -12,31 +12,26 @@ impl Behavior for TokenRequester { &mut self, client: Arc, mut messager: Messager, - ) -> Pin + Send + Sync>> { - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::AddressOf(self.token_data.name.clone())) - .unwrap(), - }; - messager.send(message).await; + ) -> EventStream { + messager + .send( + To::Agent(self.request_to.clone()), + &TokenAdminQuery::AddressOf(self.token_data.name.clone()), + ) + .await; let message = messager.get_next().await; let token_address = serde_json::from_str::
(&message.data).unwrap(); let token = ArbiterToken::new(token_address, client.clone()); self.token_data.address = Some(token_address); - let mint_data = serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { + let mint_data = TokenAdminQuery::MintRequest(MintRequest { token: self.token_data.name.clone(), mint_to: client.address(), mint_amount: 1, - })) - .unwrap(); - let mint_request = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: mint_data, - }; - messager.send(mint_request).await; + }); + messager + .send(To::Agent(self.request_to.clone()), mint_data) + .await; self.messager = Some(messager.clone()); self.client = Some(client.clone()); @@ -55,17 +50,14 @@ impl Behavior for TokenRequester { let messager = self.messager.as_ref().unwrap(); while (self.count < self.max_count.unwrap()) { debug!("sending message from requester"); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::Agent(self.request_to.clone()), - data: serde_json::to_string(&TokenAdminQuery::MintRequest(MintRequest { - token: self.token_data.name.clone(), - mint_to: self.client.as_ref().unwrap().address(), - mint_amount: 1, - })) - .unwrap(), - }; - messager.send(message).await; + let mint_data = TokenAdminQuery::MintRequest(MintRequest { + token: self.token_data.name.clone(), + mint_to: self.client.as_ref().unwrap().address(), + mint_amount: 1, + }); + messager + .send(To::Agent(self.request_to.clone()), mint_data) + .await; self.count += 1; } Some(MachineHalt) diff --git a/arbiter-engine/src/examples/minter/config.toml b/arbiter-engine/src/examples/minter/config.toml new file mode 100644 index 000000000..d2beefc3b --- /dev/null +++ b/arbiter-engine/src/examples/minter/config.toml @@ -0,0 +1,10 @@ +# top level id for the `TokenAdmin` agent +[[admin]] +# named struct and arguments for initializing the `TokenAdmin` agent +TokenAdmin = { max_count = 4, token_data = { "US Dollar Coin" = { name = "US Dollar Coin", symbol = "USDC", decimals = 18 } } } + + +# top level id for the `TokenRequester` agent +[[requester]] +# named struct and arguments for initializing the `TokenRequester` agent +TokenRequester = { max_count = 4, request_to = "admin", token_data = { name = "US Dollar Coin", symbol = "USDC", decimals = 18 } } diff --git a/arbiter-engine/src/examples/minter/mod.rs b/arbiter-engine/src/examples/minter/mod.rs index 428b05123..ba5ec2e32 100644 --- a/arbiter-engine/src/examples/minter/mod.rs +++ b/arbiter-engine/src/examples/minter/mod.rs @@ -1,13 +1,17 @@ use super::*; pub(crate) mod agents; pub(crate) mod behaviors; -pub(crate) mod token_minter; - -use std::pin::Pin; +use std::{pin::Pin, str::FromStr, time::Duration}; +use agents::{token_admin::TokenAdmin, token_requester::TokenRequester}; +use arbiter_core::data_collection::EventLogger; +use arbiter_macros::Behaviors; +use ethers::types::Address; use futures_util::Stream; +use tokio::time::timeout; use tracing::error; +use super::*; use crate::{ agent::Agent, machine::{Behavior, MachineHalt, MachineInstruction, StateMachine}, @@ -28,3 +32,68 @@ pub(crate) struct TokenData { pub decimals: u8, pub address: Option
, } + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn token_minter_simulation() { + let mut world = World::new("test_world"); + let client = RevmMiddleware::new(&world.environment, None).unwrap(); + + // Create the token admin agent + let token_admin = Agent::builder(TOKEN_ADMIN_ID); + let mut token_admin_behavior = TokenAdmin::new(Some(4)); + token_admin_behavior.add_token(TokenData { + name: TOKEN_NAME.to_owned(), + symbol: TOKEN_SYMBOL.to_owned(), + decimals: TOKEN_DECIMALS, + address: None, + }); + // Create the token requester agent + let token_requester = Agent::builder(REQUESTER_ID); + let mut token_requester_behavior = TokenRequester::new(Some(4)); + world.add_agent(token_requester.with_behavior(token_requester_behavior)); + + world.add_agent(token_admin.with_behavior(token_admin_behavior)); + + let arb = ArbiterToken::new( + Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), + client.clone(), + ); + let transfer_event = arb.transfer_filter(); + + let transfer_stream = EventLogger::builder() + .add_stream(arb.transfer_filter()) + .stream() + .unwrap(); + let mut stream = Box::pin(transfer_stream); + world.run().await; + let mut idx = 0; + + loop { + match timeout(Duration::from_secs(1), stream.next()).await { + Ok(Some(event)) => { + println!("Event received in outside world: {:?}", event); + idx += 1; + if idx == 4 { + break; + } + } + _ => { + panic!("Timeout reached. Test failed."); + } + } + } +} + +#[derive(Serialize, Deserialize, Debug, Behaviors)] +enum Behaviors { + TokenAdmin(TokenAdmin), + TokenRequester(TokenRequester), +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn config_test() { + let mut world = World::new("world"); + world.from_config::("src/examples/minter/config.toml"); + + world.run().await; +} diff --git a/arbiter-engine/src/examples/minter/token_minter.rs b/arbiter-engine/src/examples/minter/token_minter.rs deleted file mode 100644 index d1cd93c9a..000000000 --- a/arbiter-engine/src/examples/minter/token_minter.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::{str::FromStr, time::Duration}; - -use agents::{token_admin::TokenAdmin, token_requester::TokenRequester}; -use arbiter_core::data_collection::EventLogger; -use ethers::types::Address; -use tokio::time::timeout; - -use super::*; -use crate::world::World; - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn token_minter_simulation() { - let mut world = World::new("test_world"); - let client = RevmMiddleware::new(&world.environment, None).unwrap(); - - // Create the token admin agent - let token_admin = Agent::builder(TOKEN_ADMIN_ID); - let mut token_admin_behavior = TokenAdmin::new(Some(4)); - token_admin_behavior.add_token(TokenData { - name: TOKEN_NAME.to_owned(), - symbol: TOKEN_SYMBOL.to_owned(), - decimals: TOKEN_DECIMALS, - address: None, - }); - // Create the token requester agent - let token_requester = Agent::builder(REQUESTER_ID); - let mut token_requester_behavior = TokenRequester::new(Some(4)); - world.add_agent(token_requester.with_behavior(token_requester_behavior)); - - world.add_agent(token_admin.with_behavior(token_admin_behavior)); - - let arb = ArbiterToken::new( - Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), - client.clone(), - ); - let transfer_event = arb.transfer_filter(); - - let transfer_stream = EventLogger::builder() - .add_stream(arb.transfer_filter()) - .stream() - .unwrap(); - let mut stream = Box::pin(transfer_stream); - world.run().await; - let mut idx = 0; - - loop { - match timeout(Duration::from_secs(1), stream.next()).await { - Ok(Some(event)) => { - println!("Event received in outside world: {:?}", event); - idx += 1; - if idx == 4 { - break; - } - } - _ => { - panic!("Timeout reached. Test failed."); - } - } - } -} diff --git a/arbiter-engine/src/examples/mod.rs b/arbiter-engine/src/examples/mod.rs index 07162b385..592e9e647 100644 --- a/arbiter-engine/src/examples/mod.rs +++ b/arbiter-engine/src/examples/mod.rs @@ -1,8 +1,6 @@ #![warn(missing_docs)] #![allow(unused)] - //! The examples module contains example strategies. - use std::{collections::HashMap, sync::Arc}; use arbiter_bindings::bindings::arbiter_token::ArbiterToken; @@ -11,6 +9,15 @@ use ethers::types::{transaction::eip2718::TypedTransaction, Address, Log, U256}; use futures_util::{stream, StreamExt}; use super::*; -use crate::messager::{Message, Messager}; +use crate::{ + agent::Agent, + machine::{ + Behavior, CreateStateMachine, Engine, EventStream, MachineHalt, State, StateMachine, + }, + messager::{Message, Messager, To}, + world::World, +}; +#[cfg(test)] pub(crate) mod minter; +#[cfg(test)] pub(crate) mod timed_message; diff --git a/arbiter-engine/src/examples/timed_message/config.toml b/arbiter-engine/src/examples/timed_message/config.toml new file mode 100644 index 000000000..cd82e936c --- /dev/null +++ b/arbiter-engine/src/examples/timed_message/config.toml @@ -0,0 +1,5 @@ +[[ping]] +TimedMessage = { delay = 1, send_data = "ping", receive_data = "pong", startup_message = "ping" } + +[[pong]] +TimedMessage = { delay = 1, send_data = "pong", receive_data = "ping" } diff --git a/arbiter-engine/src/examples/timed_message.rs b/arbiter-engine/src/examples/timed_message/mod.rs similarity index 77% rename from arbiter-engine/src/examples/timed_message.rs rename to arbiter-engine/src/examples/timed_message/mod.rs index 383b59c7f..86da65228 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message/mod.rs @@ -1,29 +1,31 @@ -#[cfg(test)] +use super::*; const AGENT_ID: &str = "agent"; use std::{pin::Pin, time::Duration}; +use arbiter_macros::Behaviors; use ethers::types::BigEndianHash; use futures_util::Stream; +use serde::*; use tokio::time::timeout; -use self::machine::MachineHalt; use super::*; -use crate::{ - agent::Agent, - machine::{Behavior, Engine, State, StateMachine}, - messager::To, - world::World, -}; - -#[derive(Debug)] + +fn default_max_count() -> Option { + Some(3) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct TimedMessage { delay: u64, receive_data: String, send_data: String, + #[serde(skip)] messager: Option, + #[serde(default)] count: u64, + #[serde(default = "default_max_count")] max_count: Option, startup_message: Option, } @@ -54,45 +56,24 @@ impl Behavior for TimedMessage { &mut self, _client: Arc, messager: Messager, - ) -> Pin + Send + Sync>> { - trace!("Starting up `TimedMessage`."); - self.messager = Some(messager.clone()); - tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; + ) -> EventStream { if let Some(startup_message) = &self.startup_message { - messager - .clone() - .send(Message { - from: messager.id.clone().unwrap(), - to: To::All, - data: startup_message.clone(), - }) - .await; + messager.send(To::All, startup_message).await; } - trace!("Started `TimedMessage`."); - return Box::pin(messager.stream()); + self.messager = Some(messager.clone()); + return messager.stream(); } async fn process(&mut self, event: Message) -> Option { - trace!("Processing event."); - let messager = self.messager.as_ref().unwrap(); - if event.data == self.receive_data { - trace!("Event matches message. Sending a new message."); - let message = Message { - from: messager.id.clone().unwrap(), - to: To::All, - data: self.send_data.clone(), - }; - messager.send(message).await; + if event.data == serde_json::to_string(&self.receive_data).unwrap() { + let messager = self.messager.clone().unwrap(); + messager.send(To::All, self.send_data.clone()).await; self.count += 1; } if self.count == self.max_count.unwrap_or(u64::MAX) { - warn!("Reached max count. Halting behavior."); return Some(MachineHalt); } - - tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; - trace!("Processed event."); - None + return None; } } @@ -145,6 +126,7 @@ async fn ping_pong() { Some("ping".to_owned()), ); let behavior_pong = TimedMessage::new(1, "ping".to_owned(), "pong".to_owned(), Some(2), None); + world.add_agent( agent .with_behavior(behavior_ping) @@ -213,3 +195,16 @@ async fn ping_pong_two_agent() { } } } + +#[derive(Serialize, Deserialize, Debug, Behaviors)] +enum Behaviors { + TimedMessage(TimedMessage), +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn config_test() { + let mut world = World::new("world"); + world.from_config::("src/examples/timed_message/config.toml"); + + world.run().await; +} diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index c0c246ac7..99b5c63a1 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -10,6 +10,17 @@ use serde::de::DeserializeOwned; use self::messager::Messager; use super::*; +/// A type alias for a pinned, boxed stream of events. +/// +/// This stream is capable of handling items of any type that implements the +/// `Stream` trait, and it is both sendable across threads and synchronizable +/// between threads. +/// +/// # Type Parameters +/// +/// * `E`: The type of the items in the stream. +pub type EventStream = Pin + Send + Sync>>; + /// The instructions that can be sent to a [`StateMachine`]. #[derive(Clone, Debug)] pub enum MachineInstruction { @@ -53,40 +64,95 @@ pub enum State { /// The [`Behavior`] trait is the lowest level functionality that will be used /// by a [`StateMachine`]. This constitutes what each state transition will do. #[async_trait::async_trait] -pub trait Behavior: Send + Sync + Debug + 'static { +pub trait Behavior: Serialize + DeserializeOwned + Send + Sync + Debug + 'static { /// Used to start the agent. /// This is where the agent can engage in its specific start up activities /// that it can do given the current state of the world. - async fn startup( - &mut self, - client: Arc, - messager: Messager, - ) -> Pin + Send + Sync>>; + async fn startup(&mut self, client: Arc, messager: Messager) -> EventStream; /// Used to process events. /// This is where the agent can engage in its specific processing /// of events that can lead to actions being taken. async fn process(&mut self, event: E) -> Option; } - +/// A trait for creating a state machine. +/// +/// This trait is intended to be implemented by types that can be converted into +/// a state machine. A state machine, in this context, is an entity capable of +/// executing a set of instructions or operations based on its current state and +/// inputs it receives. +/// +/// Implementers of this trait should provide the logic to initialize and return +/// a new instance of a state machine, encapsulated within a `Box`. This allows for dynamic dispatch to the state machine's +/// methods, enabling polymorphism where different types of state machines can +/// be used interchangeably at runtime. +/// +/// # Returns +/// +/// - `Box`: A boxed state machine object that can be +/// dynamically dispatched. +pub trait CreateStateMachine { + /// Creates and returns a new state machine instance. + /// + /// This method consumes the implementer and returns a new instance of a + /// state machine encapsulated within a `Box`. The + /// specific type of the state machine returned can vary, allowing for + /// flexibility and reuse of the state machine logic across + /// different contexts. + fn create_state_machine(self) -> Box; +} #[async_trait::async_trait] -pub(crate) trait StateMachine: Send + Sync + Debug + 'static { +/// A trait defining the capabilities of a state machine within the system. +/// +/// This trait is designed to be implemented by entities that can execute +/// instructions based on their current state and inputs they receive. The +/// execution of these instructions is asynchronous, allowing for non-blocking +/// operations within the state machine's logic. +/// +/// Implementers of this trait must be able to be sent across threads and shared +/// among threads safely, hence the `Send`, `Sync`, and `'static` bounds. They +/// should also support debugging through the `Debug` trait. +pub trait StateMachine: Send + Sync + Debug + 'static { + /// Executes a given instruction asynchronously. + /// + /// This method takes a mutable reference to self, allowing the state + /// machine to modify its state in response to the instruction. The + /// instruction to be executed is passed as an argument, encapsulating the + /// action to be performed by the state machine. + /// + /// # Parameters + /// + /// - `instruction`: The instruction that the state machine is to execute. + /// + /// # Returns + /// + /// This method does not return a value, but it may result in state changes + /// within the implementing type or the generation of further instructions + /// or events. async fn execute(&mut self, instruction: MachineInstruction); } -/// The idea of the [`Engine`] is that it drives the [`Behavior`] of a -/// [`StateMachine`]-based entity (like an [`agent::Agent`]). -/// The [`Engine`] specifically wraps a [`Behavior`] and a [`Receiver`] of -/// events into a cohesive unit that can listen to events and pass them onto the -/// processor stage. Since the [`Engine`] is also a [`StateMachine`], its -/// generics can be collapsed into a `dyn` trait object so that, for example, -/// [`agent::Agent`]s can own multiple [`Behavior`]s with different event `` -/// types. +/// The `Engine` struct represents the core logic unit of a state machine-based +/// entity, such as an agent. It encapsulates a behavior and manages the flow +/// of events to and from this behavior, effectively driving the entity's +/// response to external stimuli. +/// +/// The `Engine` is generic over a behavior type `B` and an event type `E`, +/// allowing it to be used with a wide variety of behaviors and event sources. +/// It is itself a state machine, capable of executing instructions that +/// manipulate its behavior or react to events. +/// +/// # Fields +/// +/// - `behavior`: An optional behavior that the engine is currently managing. +/// This is where the engine's logic is primarily executed in response to +/// events. pub struct Engine where B: Behavior, { - /// The behavior the [`Engine`] runs. + /// The behavior the `Engine` runs. pub behavior: Option, /// The current state of the [`Engine`]. @@ -95,7 +161,7 @@ where /// The receiver of events that the [`Engine`] will process. /// The [`State::Processing`] stage will attempt a decode of the [`String`]s /// into the event type ``. - event_stream: Option + Send + Sync>>>, + event_stream: Option>, phantom: std::marker::PhantomData, } @@ -132,8 +198,8 @@ where #[async_trait::async_trait] impl StateMachine for Engine where - B: Behavior + Debug, - E: DeserializeOwned + Send + Sync + Debug + 'static, + B: Behavior + Debug + Serialize + DeserializeOwned, + E: DeserializeOwned + Serialize + Send + Sync + Debug + 'static, { async fn execute(&mut self, instruction: MachineInstruction) { match instruction { @@ -143,7 +209,6 @@ where let mut behavior = self.behavior.take().unwrap(); let behavior_task = tokio::spawn(async move { let id = messager.id.clone(); - debug!("starting up stream for {:?}!", id); let stream = behavior.startup(client, messager).await; debug!("startup complete for {:?}!", id); (stream, behavior) @@ -155,7 +220,6 @@ where self.execute(MachineInstruction::Process).await; } MachineInstruction::Process => { - trace!("Behavior is processing."); let mut behavior = self.behavior.take().unwrap(); let mut stream = self.event_stream.take().unwrap(); let behavior_task = tokio::spawn(async move { diff --git a/arbiter-engine/src/messager.rs b/arbiter-engine/src/messager.rs index 15d95687d..7f4032ac5 100644 --- a/arbiter-engine/src/messager.rs +++ b/arbiter-engine/src/messager.rs @@ -4,9 +4,10 @@ // TODO: It might be nice to have some kind of messaging header so that we can // pipe messages to agents and pipe messages across worlds. -use futures_util::Stream; +use serde::Serialize; use tokio::sync::broadcast::{channel, Receiver, Sender}; +use self::machine::EventStream; use super::*; /// A message that can be sent between agents. @@ -99,9 +100,9 @@ impl Messager { /// Returns a stream of messages that are either sent to [`To::All`] or to /// the agent via [`To::Agent(id)`]. - pub fn stream(mut self) -> impl Stream + Send { + pub fn stream(mut self) -> EventStream { let mut receiver = self.broadcast_receiver.take().unwrap(); - async_stream::stream! { + Box::pin(async_stream::stream! { while let Ok(message) = receiver.recv().await { match &message.to { To::All => { @@ -116,12 +117,34 @@ impl Messager { } } } - } + }) } - - /// Sends a message to the messager. - pub async fn send(&self, message: Message) { + /// Asynchronously sends a message to a specified recipient. + /// + /// This method constructs a message with the provided data and sends it to + /// the specified recipient. The recipient can either be a single agent + /// or all agents, depending on the `to` parameter. The data is + /// serialized into a JSON string before being sent. + /// + /// # Type Parameters + /// + /// - `T`: The type that can be converted into a recipient specification + /// (`To`). + /// - `S`: The type of the data being sent. Must implement `Serialize`. + /// + /// # Parameters + /// + /// - `to`: The recipient of the message. Can be an individual agent's ID or + /// a broadcast to all agents. + /// - `data`: The data to be sent in the message. This data is serialized + /// into JSON format. + pub async fn send(&self, to: To, data: S) { trace!("Sending message via messager."); + let message = Message { + from: self.id.clone().unwrap(), + to, + data: serde_json::to_string(&data).unwrap(), + }; self.broadcast_sender.send(message).unwrap(); } } diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 0cadd1cca..53981cdcf 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -15,15 +15,16 @@ //! The world module contains the core world abstraction for the Arbiter Engine. -use std::collections::VecDeque; +use std::{collections::VecDeque, fmt::Debug}; use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; use futures_util::future::join_all; +use serde::de::DeserializeOwned; use tokio::spawn; use self::{agent::AgentBuilder, machine::MachineInstruction}; use super::*; -use crate::{agent::Agent, messager::Messager}; +use crate::{agent::Agent, machine::CreateStateMachine, messager::Messager}; /// A world is a collection of agents that use the same type of provider, e.g., /// operate on the same blockchain or same `Environment`. The world is @@ -50,6 +51,7 @@ pub struct World { pub messager: Messager, } +use std::{fs::File, io::Read}; impl World { /// Creates a new [`World`] with the given identifier and provider. pub fn new(id: &str) -> Self { @@ -61,30 +63,149 @@ impl World { } } - /// Adds an agent to the world. + /// Builds and adds agents to the world from a configuration file. + /// + /// This method reads a configuration file specified by `config_path`, which + /// should be a TOML file containing the definitions of agents and their + /// behaviors. Each agent is identified by a unique string key, and + /// associated with a list of behaviors. These behaviors are + /// deserialized into instances that implement the `CreateStateMachine` + /// trait, allowing them to be converted into state machines that define + /// the agent's behavior within the world. + /// + /// # Type Parameters + /// + /// - `C`: The type of the behavior component that each agent will be + /// associated with. + /// This type must implement the `CreateStateMachine`, `Serialize`, + /// `DeserializeOwned`, and `Debug` traits. + /// + /// # Arguments + /// + /// - `config_path`: A string slice that holds the path to the configuration + /// file + /// relative to the current working directory. + /// + /// # Panics + /// + /// This method will panic if: + /// - The current working directory cannot be determined. + /// - The configuration file specified by `config_path` cannot be opened. + /// - The configuration file cannot be read into a string. + /// - The contents of the configuration file cannot be deserialized into the + /// expected + /// `HashMap>` format. + /// + /// # Examples + /// + /// Assuming a TOML file named `agents_config.toml` exists in the current + /// working directory with the following content: + /// + /// ```toml + /// [[agent1]] + /// BehaviorTypeA = { ... } , + /// [[agent1]] + /// BehaviorTypeB = { ... } + /// + /// [agent2] + /// BehaviorTypeC = { ... } + /// ``` + pub fn from_config( + &mut self, + config_path: &str, + ) { + let cwd = std::env::current_dir().expect("Failed to determine current working directory"); + let path = cwd.join(config_path); + let mut file = File::open(path).expect("Failed to open configuration file"); + + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Failed to read configuration file to string"); + + let agents_map: HashMap> = + toml::from_str(&contents).expect("Failed to deserialize configuration file"); + + for (agent, behaviors) in agents_map { + let mut next_agent = Agent::builder(&agent); + for behavior in behaviors { + let engine = behavior.create_state_machine(); + next_agent = next_agent.with_engine(engine); + } + self.add_agent(next_agent); + } + } + + /// Adds an agent, constructed from the provided `AgentBuilder`, to the + /// world. + /// + /// This method takes an `AgentBuilder` instance, extracts its identifier, + /// and uses it to create both a `RevmMiddleware` client and a + /// `Messager` specific to the agent. It then builds the `Agent` from + /// the `AgentBuilder` using these components. Finally, the newly + /// created `Agent` is inserted into the world's internal collection of + /// agents. + /// + /// # Panics + /// + /// This method will panic if: + /// - It fails to create a `RevmMiddleware` client for the agent. + /// - The `AgentBuilder` fails to build the `Agent`. + /// - The world's internal collection of agents is not initialized. + /// + /// # Examples + /// + /// Assuming you have an `AgentBuilder` instance named `agent_builder`: + /// + /// ```ignore + /// world.add_agent(agent_builder); + /// ``` + /// + /// This will add the agent defined by `agent_builder` to the world. pub fn add_agent(&mut self, agent_builder: AgentBuilder) { let id = agent_builder.id.clone(); - let client = RevmMiddleware::new(&self.environment, Some(&id)).unwrap(); + let client = RevmMiddleware::new(&self.environment, Some(&id)) + .expect("Failed to create RevmMiddleware client for agent"); let messager = self.messager.for_agent(&id); - let agent = agent_builder.build(client, messager).unwrap(); - let agents = self.agents.as_mut().unwrap(); + let agent = agent_builder + .build(client, messager) + .expect("Failed to build agent from AgentBuilder"); + let agents = self + .agents + .as_mut() + .expect("Agents collection not initialized"); agents.insert(id.to_owned(), agent); } - /// Runs all of the [`Agent`]s and their [`crate::machine::Behavior`]s in - /// the world in parallel. + /// Executes all agents and their behaviors concurrently within the world. + /// + /// This method takes all the agents registered in the world and runs their + /// associated behaviors in parallel. Each agent's behaviors are + /// executed with their respective messaging and client context. This + /// method ensures that all agents and their behaviors are started + /// simultaneously, leveraging asynchronous execution to manage concurrent + /// operations. + /// + /// # Errors + /// + /// Returns an error if no agents are found in the world, possibly + /// indicating that the world has already been run or that no agents + /// were added prior to execution. pub async fn run(&mut self) -> Result<()> { let mut tasks = vec![]; + // Retrieve the agents, erroring if none are found. let agents = self .agents .take() .ok_or_else(|| anyhow!("No agents found! Has the world already been run?"))?; + // Prepare a queue for messagers corresponding to each behavior engine. let mut messagers = VecDeque::new(); + // Populate the messagers queue. for (_, agent) in agents.iter() { for _ in &agent.behavior_engines { messagers.push_back(agent.messager.clone()); } } + // For each agent, spawn a task for each of its behavior engines. for (_, mut agent) in agents { for mut engine in agent.behavior_engines.drain(..) { let client = agent.client.clone(); @@ -96,6 +217,7 @@ impl World { })); } } + // Await the completion of all tasks. join_all(tasks).await; Ok(()) } diff --git a/arbiter-macros/Cargo.toml b/arbiter-macros/Cargo.toml new file mode 100644 index 000000000..8ea92a884 --- /dev/null +++ b/arbiter-macros/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "arbiter-macros" +version = "0.1.0" + +[lib] +proc-macro = true + +[dependencies] +syn.workspace = true +quote = "1.0.35" diff --git a/arbiter-macros/src/lib.rs b/arbiter-macros/src/lib.rs new file mode 100644 index 000000000..061335bfe --- /dev/null +++ b/arbiter-macros/src/lib.rs @@ -0,0 +1,49 @@ +extern crate proc_macro; +extern crate quote; +extern crate syn; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields}; + +#[proc_macro_derive(Behaviors)] +pub fn create_behavior_from_enum(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; // The name of the enum + + let enum_data = if let Data::Enum(DataEnum { variants, .. }) = input.data { + variants + } else { + // Not an enum, so panic or handle as needed + panic!("CreateBehaviorFromEnum is only defined for enums"); + }; + + let match_arms = enum_data.into_iter().map(|variant| { + let variant_name = variant.ident; + let _inner_type = if let Fields::Unnamed(fields) = variant.fields { + fields.unnamed.first().unwrap().ty.clone() + } else { + panic!("Expected unnamed fields in enum variant"); + }; + + quote! { + #name::#variant_name(inner) => { + Box::new(Engine::new(inner)) + } + } + }); + + let expanded = quote! { + + impl CreateStateMachine for #name { + fn create_state_machine(self) -> Box { + match self { + #(#match_arms,)* + } + } + } + }; + + TokenStream::from(expanded) +} From ad701cb88a915a71ce010897ed57d4825b700fbd Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Mon, 5 Feb 2024 16:25:11 -0700 Subject: [PATCH 30/38] mdbook contributions for new crates (#845) docs(arbiter-engine / arbiter-macros): mdbook contributions for new crates and their usage. --- Cargo.lock | 2 +- .../src/examples/minter/token_minter.rs | 75 +++++++++++++++ .../src/examples/timed_message/config.toml | 6 ++ .../src/examples/timed_message/mod.rs | 4 + arbiter-macros/Cargo.toml | 1 - documentation/src/SUMMARY.md | 7 +- .../getting_started/setting_up_simulations.md | 2 +- documentation/src/usage/arbiter_engine.md | 5 - .../arbiter_engine/agents_and_engines.md | 58 +++++++++++ .../src/usage/arbiter_engine/behaviors.md | 95 +++++++++++++++++++ .../src/usage/arbiter_engine/configuration.md | 66 +++++++++++++ .../src/usage/arbiter_engine/index.md | 27 ++++++ .../arbiter_engine/worlds_and_universes.md | 85 +++++++++++++++++ documentation/src/usage/arbiter_macros.md | 5 + 14 files changed, 429 insertions(+), 9 deletions(-) create mode 100644 arbiter-engine/src/examples/minter/token_minter.rs delete mode 100644 documentation/src/usage/arbiter_engine.md create mode 100644 documentation/src/usage/arbiter_engine/agents_and_engines.md create mode 100644 documentation/src/usage/arbiter_engine/behaviors.md create mode 100644 documentation/src/usage/arbiter_engine/configuration.md create mode 100644 documentation/src/usage/arbiter_engine/index.md create mode 100644 documentation/src/usage/arbiter_engine/worlds_and_universes.md create mode 100644 documentation/src/usage/arbiter_macros.md diff --git a/Cargo.lock b/Cargo.lock index 0d78808a6..0010a7c11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "arbiter-macros" -version = "0.1.0" +version = "0.0.0" dependencies = [ "quote", "syn 2.0.48", diff --git a/arbiter-engine/src/examples/minter/token_minter.rs b/arbiter-engine/src/examples/minter/token_minter.rs new file mode 100644 index 000000000..e1df78e29 --- /dev/null +++ b/arbiter-engine/src/examples/minter/token_minter.rs @@ -0,0 +1,75 @@ +use std::{str::FromStr, time::Duration}; + +use agents::{token_admin::TokenAdmin, token_requester::TokenRequester}; +use arbiter_core::data_collection::EventLogger; +use arbiter_macros::Behaviors; +use ethers::types::Address; +use tokio::time::timeout; + +use super::*; +use crate::world::World; + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn token_minter_simulation() { + let mut world = World::new("test_world"); + let client = RevmMiddleware::new(&world.environment, None).unwrap(); + + // Create the token admin agent + let token_admin = Agent::builder(TOKEN_ADMIN_ID); + let mut token_admin_behavior = TokenAdmin::new(Some(4)); + token_admin_behavior.add_token(TokenData { + name: TOKEN_NAME.to_owned(), + symbol: TOKEN_SYMBOL.to_owned(), + decimals: TOKEN_DECIMALS, + address: None, + }); + // Create the token requester agent + let token_requester = Agent::builder(REQUESTER_ID); + let mut token_requester_behavior = TokenRequester::new(Some(4)); + world.add_agent(token_requester.with_behavior(token_requester_behavior)); + + world.add_agent(token_admin.with_behavior(token_admin_behavior)); + + let arb = ArbiterToken::new( + Address::from_str("0x240a76d4c8a7dafc6286db5fa6b589e8b21fc00f").unwrap(), + client.clone(), + ); + let transfer_event = arb.transfer_filter(); + + let transfer_stream = EventLogger::builder() + .add_stream(arb.transfer_filter()) + .stream() + .unwrap(); + let mut stream = Box::pin(transfer_stream); + world.run().await; + let mut idx = 0; + + loop { + match timeout(Duration::from_secs(1), stream.next()).await { + Ok(Some(event)) => { + println!("Event received in outside world: {:?}", event); + idx += 1; + if idx == 4 { + break; + } + } + _ => { + panic!("Timeout reached. Test failed."); + } + } + } +} + +#[derive(Serialize, Deserialize, Debug, Behaviors)] +enum Behaviors { + TokenAdmin(TokenAdmin), + TokenRequester(TokenRequester), +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn config_test() { + let mut world = World::new("world"); + world.build_with_config::("src/examples/minter/config.toml"); + + world.run().await; +} diff --git a/arbiter-engine/src/examples/timed_message/config.toml b/arbiter-engine/src/examples/timed_message/config.toml index cd82e936c..8ef7e5ddd 100644 --- a/arbiter-engine/src/examples/timed_message/config.toml +++ b/arbiter-engine/src/examples/timed_message/config.toml @@ -1,5 +1,11 @@ [[ping]] TimedMessage = { delay = 1, send_data = "ping", receive_data = "pong", startup_message = "ping" } +[[ping]] +TimedMessage = { delay = 1, send_data = "zam", receive_data = "zim", startup_message = "zam" } + [[pong]] TimedMessage = { delay = 1, send_data = "pong", receive_data = "ping" } + +[[pong]] +TimedMessage = { delay = 1, send_data = "zim", receive_data = "zam" } diff --git a/arbiter-engine/src/examples/timed_message/mod.rs b/arbiter-engine/src/examples/timed_message/mod.rs index 86da65228..2fd858d25 100644 --- a/arbiter-engine/src/examples/timed_message/mod.rs +++ b/arbiter-engine/src/examples/timed_message/mod.rs @@ -16,6 +16,10 @@ fn default_max_count() -> Option { Some(3) } +fn default_max_count() -> Option { + Some(3) +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct TimedMessage { delay: u64, diff --git a/arbiter-macros/Cargo.toml b/arbiter-macros/Cargo.toml index 8ea92a884..3754898f2 100644 --- a/arbiter-macros/Cargo.toml +++ b/arbiter-macros/Cargo.toml @@ -1,6 +1,5 @@ [package] name = "arbiter-macros" -version = "0.1.0" [lib] proc-macro = true diff --git a/documentation/src/SUMMARY.md b/documentation/src/SUMMARY.md index 81d2ed47c..34d6472f8 100644 --- a/documentation/src/SUMMARY.md +++ b/documentation/src/SUMMARY.md @@ -10,7 +10,12 @@ - [Arbiter Core](./usage/arbiter_core/index.md) - [Environment](./usage/arbiter_core/environment.md) - [Middleware](./usage/arbiter_core/middleware.md) - - [Arbiter Engine](./usage/arbiter_engine.md) + - [Arbiter Engine](./usage/arbiter_engine/index.md) + - [Behaviors](./usage/arbiter_engine/behaviors.md) + - [Agents and Engines](./usage/arbiter_engine/agents_and_engines.md) + - [Worlds and Universes](./usage/arbiter_engine/worlds_and_universes.md) + - [Configuration](./usage/arbiter_engine/configuration.md) + - [Arbiter Macros](./usage/arbiter_macros.md) - [Techniques](./usage/techniques/index.md) - [Stateful Testing](./usage/techniques/stateful_testing.md) - [Anomaly Detection](./usage/techniques/anomaly_detection.md) diff --git a/documentation/src/getting_started/setting_up_simulations.md b/documentation/src/getting_started/setting_up_simulations.md index 5c9b6f475..2ec682d0f 100644 --- a/documentation/src/getting_started/setting_up_simulations.md +++ b/documentation/src/getting_started/setting_up_simulations.md @@ -38,7 +38,7 @@ The stage is now set for you to begin writing your simulation code. This will consist of the following: - Creating a [`Environment`](../usage/arbiter_core/environment.md) for your simulation. - Creating agents. -(TODO: More documentation here for [`arbiter-engine`](../usage/arbiter_engine.md)) +(TODO: More documentation here for [`arbiter-engine`](../usage/arbiter_engine/index.md)) - Creating a [`RevmMiddleware`](../usage/arbiter_core/middleware.md) for each agent in your simulation. - Deploy contracts using the binding's `MyContract::deploy()` method which will need a client `Arc` and constructor arguments passed as a tuple. Or, if you want to use a forked state, use the binding's `MyContract::new()` method and pass it the relevant client and address. diff --git a/documentation/src/usage/arbiter_engine.md b/documentation/src/usage/arbiter_engine.md deleted file mode 100644 index f52d104a0..000000000 --- a/documentation/src/usage/arbiter_engine.md +++ /dev/null @@ -1,5 +0,0 @@ -# Arbiter Engine - -WORK IN PROGRESS - NOT STABLE FOR USE YET. - -MORE WILL BE ADDED HERE SOON. \ No newline at end of file diff --git a/documentation/src/usage/arbiter_engine/agents_and_engines.md b/documentation/src/usage/arbiter_engine/agents_and_engines.md new file mode 100644 index 000000000..193f53c41 --- /dev/null +++ b/documentation/src/usage/arbiter_engine/agents_and_engines.md @@ -0,0 +1,58 @@ +# Agents and Engines +`Behavior`s are the heartbeat of your `Agent`s and they are wrapped by `Engine`s. +The main idea here is that you can have an `Agent` that has as many `Behavior`s as you like, and each of those behaviors may process different types of events. +This gives flexibility in how you want to design your `Agent`s and what emergent properties you want to observe. + +## Design Principles +It is up to you whether or not you prefer to have `Agent`s have multiple `Behavior`s or not or if you want them to have a single `Behavior` that processes all events. +For the former case, you will build `Behavior` for different types `E` and place these inside of an `Agent`. +For the latter, you will create an `enum` that wraps all the different types of events that you want to process and then implement `Behavior` on that `enum`. +The latter will also require a `stream::select` type of operation to merge all the different event streams into one, though this is not difficult to do. + +## `struct Agent` +The `Agent` struct is the primary struct that you will be working with. +It contains an ID, a client (`Arc`) that provides means to send calls and transactions to an Arbiter `Environment`, and a `Messager`. +It looks like this: +```rust +pub struct Agent { + pub id: String, + pub messager: Messager, + pub client: Arc, + pub(crate) behavior_engines: Vec>, +} +``` + +Your work will only be to define `Behavior`s and then add them to an `Agent` with the `Agent::with_behavior` method. + +The `Agent` is inactive until it is paired with a `World` and then it is ready to be run. +This is handled by creating a world (see: [Worlds and Universes](./worlds_and_universes.md)) and then adding the `Agent` to the `World` with the `World::add_agent` method. +Some of the intermediary representations are below: + +#### `struct AgentBuilder` +The `AgentBuilder` struct is a builder pattern for creating `Agent`s. +This is essentially invisible for the end-user, but it is used internally so that `Agent`s can be built in a more ergonomic way. + +#### `struct Engine` +Briefly, the `Engine` struct provides the machinery to run a `Behavior` and it is not necessary for you to handle this directly. +The purpose of this design is to encapsulate the `Behavior` and the event stream `Stream` that the `Behavior` will use for processing. +This encapsulation also allows the `Agent` to hold onto `Behavior` for various different types of `E` all at once. + +## Example +Let's create an `Agent` that has two `Behavior`s using the `Replier` behavior from before. +```rust +use arbiter_engine::agent::Agent; +use crate::Replier; + +fn setup() { + let ping_replier = Replier::new("ping", "pong", 5, None); + let pong_replier = Replier::new("pong", "ping", 5, Some("ping")); + let agent = Agent::new("my_agent") + .with_behavior(ping_replier) + .with_behavior(pong_replier); +} +``` +In this example, we have created an `Agent` with two `Replier` behaviors. +The `ping_replier` will reply to a message with "pong" and the `pong_replier` will reply to a message with "ping". +Given that the `pong_replier` has a `startup_message` of "ping", it will send a message to everyone (including the "my_agent" itself who holds the `ping_replier` behavior) when it starts up. +This will start a chain of messages that will continue in a "ping" "pong" fashion until the `max_count` is reached. +``` \ No newline at end of file diff --git a/documentation/src/usage/arbiter_engine/behaviors.md b/documentation/src/usage/arbiter_engine/behaviors.md new file mode 100644 index 000000000..dd51377e6 --- /dev/null +++ b/documentation/src/usage/arbiter_engine/behaviors.md @@ -0,0 +1,95 @@ +# Behaviors +The design of `arbiter-engine` is centered around the concept of `Agent`s and `Behavior`s. +At the core, we place `Behavior`s as the event-driven machinery that defines the entire simulation. +What we want is that your simulation is defined completely with how your `Agent`s behaviors are defined. +All you should be looking for is how to define your `Agent`s behaviors and what emergent properties you want to observe. + +## `trait Behavior` +To define a `Behavior`, you need to implement the `Behavior` trait on a struct of your own design. +The `Behavior` trait is defined as follows: +```rust +pub trait Behavior { + fn startup(&mut self, client: Arc, messager: Messager) -> EventStream; + fn process(&mut self, event: E) -> Option; +} +``` +To outline the design principles here: +- `startup` is a method that initializes the `Behavior` and returns an `EventStream` that the `Behavior` will use for processing. + - This method yields a client and messager from the `Agent` that owns the `Behavior`. + In this method you should take the client and messager and store them in your struct if you will need them in the processing of events. + Note, you may not need both or even either! +- `process` is a method that processes an event of type `E` and returns an `Option`. + - If `process` returns `Some(MachineHalt)`, then the `Behavior` will stop processing events completely. + +**Summary:** A `Behavior` is tantamount to engage the processing some events of type `E`. + +**Advice:** `Behavior`s should be limited in scope and should be a simplistic action driven from a single event. +Otherwise you risk having a `Behavior` that is too complex and difficult to understand and maintain. + +### Example +To see this in use, let's take a look at an example of a `Behavior` called `Replier` that replies to a message with a message of its own, and stops once it has replied a certain number of times. +```rust +use std::sync::Arc; +use arbiter_core::middleware::RevmMiddleware; +use arbiter_engine::{ + machine::{Behavior, MachineHalt}, + messager::{Messager, To}, + EventStream}; + +pub struct Replier { + receive_data: String, + send_data: String, + max_count: u64, + startup_message: Option, + count: u64, + messager: Option, +} + +impl Replier { + pub fn new( + receive_data: String, + send_data: String, + max_count: u64, + startup_message: Option, + ) -> Self { + Self { + receive_data, + send_data, + startup_message, + max_count, + count: 0, + messager: None, + } + } +} + +impl Behavior for Replier { + async fn startup( + &mut self, + client: Arc, + messager: Messager, + ) -> EventStream { + if let Some(startup_message) = &self.startup_message { + messager.send(To::All, startup_message).await; + } + self.messager = Some(messager.clone()); + return messager.stream(); + } + + async fn process(&mut self, event: Message) -> Option { + if event.data == self.receive_data { + self.messager.unwrap().messager.send(To::All, send_data).await; + self.count += 1; + } + if self.count == self.max_count { + return Some(MachineHalt); + } + return None + } +} +``` +In this example, we have a `Behavior` that upon `startup` will see if there is a `startup_message` assigned and if so, send it to all `Agent`s that are listening to their `Messager`. +Then, it will store the `Messager` for sending messages later on and start a stream of incoming messages so that we have `E = Message` in this case. +Once these are completed, the `Behavior` automatically transitions into the `process`ing stage where events are popped from the `EventStream` and fed to the `process` method. + +As messages come in, if the `receive_data` matches the incoming message, then the `Behavior` will send the `send_data` to all `Agent`s listening to their `Messager` a message with data `send_data`. \ No newline at end of file diff --git a/documentation/src/usage/arbiter_engine/configuration.md b/documentation/src/usage/arbiter_engine/configuration.md new file mode 100644 index 000000000..071145db1 --- /dev/null +++ b/documentation/src/usage/arbiter_engine/configuration.md @@ -0,0 +1,66 @@ +# Configuration +To make it so you rarely have to recompile your project, you can use a configuration file to set the parameters of your simulation once your `Behavior`s have been defined. +Let's take a look at how to do this. + +## Behavior Enum +It is good practice to take your `Behavior`s and wrap them in an `enum` so that you can use them in a configuration file. +For instance, let's say you have two struct `Maker` and `Taker` that implement `Behavior` for their own `E`. +Then you can make your `enum` like this: +```rust +use arbiter_macros::Behaviors; + +#[derive(Behaviors)] +pub enum Behaviors { + Maker(Maker), + Taker(Taker), +} +``` +Notice that we used the `Behaviors` derive macro from the `arbiter_macros` crate. +This macro will generate an implementation of a `CreateStateMachine` trait for the `Behaviors` enum and ultimately save you from having to write a lot of boilerplate code. +The macro solely requires that the `Behavior`s you have implement the `Behavior` trait and that the necessary imports are in scope. + +## Configuration File +Now that you have your `enum` of `Behavior`s, you can configure your `World` and the `Agent`s inside of it from configuration file. +Since the `World` and your simulation is completely defined by the `Agent` `Behavior`s you make, all you need to do is specify your `Agent`s in the configuration file. +For example, let's say we have the `Replier` behavior from before, so we have: +```rust +#[derive(Behaviors)] +pub enum Behaviors { + Replier(Replier), +} + +pub struct Replier { + receive_data: String, + send_data: String, + max_count: u64, + startup_message: Option, + count: u64, + messager: Option, +} +``` +Then, we can specify the "ping" and "pong" `Behavior`s like this: +```toml +[[my_agent]] +Replier = { send_data = "ping", receive_data = "pong", max_count = 5, startup_message = "ping" } + +[[my_agent]] +Replier = { send_data = "pong", receive_data = "ping", max_count = 5 } +``` +If you instead wanted to specify two `Agent`s "Alice" and "Bob" each with one of the `Replier` `Behavior`s, you could do it like this: +```toml +[[alice]] +Replier = { send_data = "ping", receive_data = "pong", max_count = 5, startup_message = "ping" } + +[[bob]] +Replier = { send_data = "pong", receive_data = "ping", max_count = 5 } +``` + +## Loading the Configuration +Once you have your configuration file located at `./path/to/config.toml`, you can load it and run your simulation like this: +```rust +fn main() { + let world = World::from_config("./path/to/config.toml")?; + world.run().await; +} +``` +At the moment, we do not configure `Universe`s from a configuration file, but this is a feature that is planned for the future. \ No newline at end of file diff --git a/documentation/src/usage/arbiter_engine/index.md b/documentation/src/usage/arbiter_engine/index.md new file mode 100644 index 000000000..e3104ad90 --- /dev/null +++ b/documentation/src/usage/arbiter_engine/index.md @@ -0,0 +1,27 @@ +# Arbiter Engine +`arbiter-engine` provides the machinery to build agent based / event driven simulations and should be the primary entrypoint for using Arbiter. +The goal of this crate is to abstract away the work required to set up agents, their behaviors, and the worlds they live in. +At the moment, all interaction of agents is done through the `arbiter-core` crate and is meant to be for local simulations and it is not yet generalized for the case of live network automation. + +## Heirarchy +The primary components of `arbiter-engine` are, from the bottom up: +- `Behavior`: This is an event-driven behavior that takes in some item of type `E` and can act on that. +The `Behavior` has two methods: `startup` and `process`. + - `startup` is meant to initialize the `Behavior` and any context around it. + An example could be an agent that deploys token contracts on startup. + - `process` is meant to be a stage that runs on every event that comes in. + An example could be an agent that deployed token contracts on startup, and now wants to process queries about the tokens deployed in the simulation (e.g., what their addresses are). +- `Engine` and `StateMachine`: The `Engine` is a struct that implements the `StateMachine` trait as an entrypoint to run `Behavior`s. + - `Engine` is a struct owns a `B: Behavior` and the event stream `Stream` that the `Behavior` will use for processing. + - `StateMachine` is a trait that reduces the interface to `Engine` to a single method: `execute`. + This trait allows `Agent`s to have multiple behaviors that may not use the same event type. +- `Agent` a struct that contains an ID, a client (`Arc`) that provides means to send calls and transactions to an Arbiter `Environment`, and a `Messager`. + - `Messager` is a struct that owns a `Sender` and `Receiver` for sending and receiving messages. + This is a way for `Agent`s to communicate with each other. + It can also be streamed and used for processing messages in a `Behavior`. + - `Agent` also owns a `Vec>` which is a list of `StateMachine`s that the `Agent` will run. + This is a way for `Agent`s to have multiple `Behavior`s that may not use the same event type. +- `World` is a struct that has an ID, an Arbiter `Environment`, a mapping of `Agent`s, and a `Messager`. + - The `World` is tasked with letting `Agent`s join in, and when they do so, to connect them to the `Environment` with a client and `Messager` with the `Agent`'s ID. +- `Universe` is a struct that wraps a mapping of `World`s. + - The `Universe` is tasked with letting `World`s join in and running those `World`s in parallel. diff --git a/documentation/src/usage/arbiter_engine/worlds_and_universes.md b/documentation/src/usage/arbiter_engine/worlds_and_universes.md new file mode 100644 index 000000000..3503ce2cf --- /dev/null +++ b/documentation/src/usage/arbiter_engine/worlds_and_universes.md @@ -0,0 +1,85 @@ +# Worlds and Universes +`Universes` are the top-level struct that you will be working with in the Arbiter Engine. +They are tasked with letting `World`s join in and running those `World`s in parallel. +By no means are you required to use `Universe`s, but they will be useful for running multiple simulations at once or, in the future, they will allow for running `World`s that have different internal environments. +For instance, one could have a `World` that consists of `Agent`s acting on the Ethereum mainnet, another `World` that consists of `Agent`s acting on Optimism, and finally a `World` that has an Arbiter `Environment` as the network analogue. +Using these in tandem is a long-term goal of the Arbiter project. + +Depending on your needs, you will either use the `Universe` if you want to run multiple `World`s in parallel or you will use the `World` if you only want to run a single simulation. +The choice is yours. + +## `struct Universe` +The `Universe` struct looks like this: +```rust +pub struct Universe { + worlds: Option>, + world_tasks: Option>>, +} +``` +The `Universe` is a struct that wraps a mapping of `World`s where the key of the map is the `World`'s ID. +Also, the `Universe` manages the running of those `World`s in parallel by storing the running `World`s as tasks. +In the future, more introspection and control will be added to the `Universe` to allow for debugging and managing the running `World`s. + +The `Universe::run_worlds` currently iterates through the `World`s and starts them in concurrent tasks. + +## `struct World` +The `World` struct looks like this: +```rust +pub struct World { + pub id: String, + pub agents: Option>, + pub environment: Environment, + pub messager: Messager, +} +``` +The `World` is a struct that has an ID, an Arbiter `Environment`, a mapping of `Agent`s, and a `Messager`. +The `World` is tasked with letting `Agent`s join in, and when they do so, to connect them to the `Environment` with a client and `Messager` with the `Agent`'s ID. +Then the `World` stores the `Agent`s in a map where the key is the `Agent`'s ID. + +The main methods to use with the world is `World::add_agent` which adds an agent to the `World` and `World::run` which will engage all of the `Agent` `Behavior`s. + +In future development, the `World` will be generic over your choice of `Provider` that encapsulates the Ethereum-like execution environment you want to use (e.g., Ethereum mainnet, Optimism, or an Arbiter `Environment`). + +## Example +Let's first do a quick example where we take a `World` and add an `Agent` to it. +```rust +use arbiter_engine::{agent::Agent, world::World}; +use crate::Replier; + +fn setup_world(id: &str) -> World { + let ping_replier = Replier::new("ping", "pong", 5, None); + let pong_replier = Replier::new("pong", "ping", 5, Some("ping")); + let agent = Agent::new("my_agent") + .with_behavior(ping_replier) + .with_behavior(pong_replier); + let mut world = World::new(id); + world.add_agent(agent); +} + +fn main() { + let world = setup_world("my_world"); + world.run().await; +} +``` +If you wanted to extend this to use a `Universe`, you would simply create a `Universe` and add the `World` to it. +```rust +use arbiter_engine::{agent::Agent, world::World}; +use crate::Replier; + +fn setup_world(id: &str) -> World { + let ping_replier = Replier::new("ping", "pong", 5, None); + let pong_replier = Replier::new("pong", "ping", 5, Some("ping")); + let agent = Agent::new("my_agent") + .with_behavior(ping_replier) + .with_behavior(pong_replier); + let mut world = World::new(id); + world.add_agent(agent); +} + +fn main() { + let mut universe = Universe::new(); + universe.add_world(setup_world("my_world")); + universe.add_world(setup_world("my_other_world")); + universe.run_worlds().await; +} +``` \ No newline at end of file diff --git a/documentation/src/usage/arbiter_macros.md b/documentation/src/usage/arbiter_macros.md new file mode 100644 index 000000000..ee85cd6b2 --- /dev/null +++ b/documentation/src/usage/arbiter_macros.md @@ -0,0 +1,5 @@ +# Arbiter macros +`arbiter_macros` provides a set of macros to help with the use of `arbiter-engine` and `arbiter-core`. +At the moment, we only have one proc macro: `#[derive(Behaviors)]`. +This macro will generate an implementation of a `CreateStateMachine` trait for the `Behaviors` enum and ultimately save you from having to write a lot of boilerplate code. +See the [Configuration](./arbiter_engine/configuration.md) page for more information on how to use this macro. \ No newline at end of file From bbf5868f134c571a6ace6704e1c76be29379ef4c Mon Sep 17 00:00:00 2001 From: Matt Czernik Date: Wed, 7 Feb 2024 13:53:25 -0500 Subject: [PATCH 31/38] Fix: engine::new() visibility (#854) * fix: engine `new` visibility --- Cargo.lock | 2 +- arbiter-engine/src/examples/timed_message/mod.rs | 4 ---- arbiter-engine/src/machine.rs | 2 +- arbiter-macros/Cargo.toml | 1 + documentation/src/usage/arbiter_engine/index.md | 2 +- .../src/usage/arbiter_engine/worlds_and_universes.md | 4 ++-- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0010a7c11..0d78808a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "arbiter-macros" -version = "0.0.0" +version = "0.1.0" dependencies = [ "quote", "syn 2.0.48", diff --git a/arbiter-engine/src/examples/timed_message/mod.rs b/arbiter-engine/src/examples/timed_message/mod.rs index 2fd858d25..86da65228 100644 --- a/arbiter-engine/src/examples/timed_message/mod.rs +++ b/arbiter-engine/src/examples/timed_message/mod.rs @@ -16,10 +16,6 @@ fn default_max_count() -> Option { Some(3) } -fn default_max_count() -> Option { - Some(3) -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct TimedMessage { delay: u64, diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 99b5c63a1..b8d8071e9 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -185,7 +185,7 @@ where E: DeserializeOwned + Send + Sync + 'static, { /// Creates a new [`Engine`] with the given [`Behavior`] and [`Receiver`]. - pub(crate) fn new(behavior: B) -> Self { + pub fn new(behavior: B) -> Self { Self { behavior: Some(behavior), state: State::Uninitialized, diff --git a/arbiter-macros/Cargo.toml b/arbiter-macros/Cargo.toml index 3754898f2..8ea92a884 100644 --- a/arbiter-macros/Cargo.toml +++ b/arbiter-macros/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "arbiter-macros" +version = "0.1.0" [lib] proc-macro = true diff --git a/documentation/src/usage/arbiter_engine/index.md b/documentation/src/usage/arbiter_engine/index.md index e3104ad90..3f8167018 100644 --- a/documentation/src/usage/arbiter_engine/index.md +++ b/documentation/src/usage/arbiter_engine/index.md @@ -3,7 +3,7 @@ The goal of this crate is to abstract away the work required to set up agents, their behaviors, and the worlds they live in. At the moment, all interaction of agents is done through the `arbiter-core` crate and is meant to be for local simulations and it is not yet generalized for the case of live network automation. -## Heirarchy +## Hierarchy The primary components of `arbiter-engine` are, from the bottom up: - `Behavior`: This is an event-driven behavior that takes in some item of type `E` and can act on that. The `Behavior` has two methods: `startup` and `process`. diff --git a/documentation/src/usage/arbiter_engine/worlds_and_universes.md b/documentation/src/usage/arbiter_engine/worlds_and_universes.md index 3503ce2cf..1f6315e64 100644 --- a/documentation/src/usage/arbiter_engine/worlds_and_universes.md +++ b/documentation/src/usage/arbiter_engine/worlds_and_universes.md @@ -56,7 +56,7 @@ fn setup_world(id: &str) -> World { world.add_agent(agent); } -fn main() { +async fn run() { let world = setup_world("my_world"); world.run().await; } @@ -82,4 +82,4 @@ fn main() { universe.add_world(setup_world("my_other_world")); universe.run_worlds().await; } -``` \ No newline at end of file +``` From c0a73c316bb3af94168b02e5ed0eb1e17d9c171d Mon Sep 17 00:00:00 2001 From: Waylon Jepsen <57912727+0xJepsen@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:07:24 -0700 Subject: [PATCH 32/38] =?UTF-8?q?chore:=20documentation=20=20refactor=20?= =?UTF-8?q?=F0=9F=8C=B1=20(#847)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit documentation gardening 🌱 --- documentation/src/SUMMARY.md | 6 +-- documentation/src/auditing/auditing.md | 9 ---- documentation/src/getting_started/index.md | 15 ++++-- .../src/getting_started/installation.md | 22 --------- .../getting_started/setting_up_simulations.md | 48 ------------------- .../arbiter_engine/agents_and_engines.md | 2 +- .../src/usage/arbiter_engine/behaviors.md | 2 +- documentation/src/usage/index.md | 21 ++++---- .../src/usage/techniques/anomaly_detection.md | 7 +++ documentation/src/usage/techniques/index.md | 7 +++ 10 files changed, 39 insertions(+), 100 deletions(-) delete mode 100644 documentation/src/auditing/auditing.md delete mode 100644 documentation/src/getting_started/installation.md delete mode 100644 documentation/src/getting_started/setting_up_simulations.md diff --git a/documentation/src/SUMMARY.md b/documentation/src/SUMMARY.md index 34d6472f8..d0ec6eebe 100644 --- a/documentation/src/SUMMARY.md +++ b/documentation/src/SUMMARY.md @@ -1,12 +1,8 @@ # Summary [Arbiter](./index.md) - [Getting Started](./getting_started/index.md) - - [Installation](./getting_started/installation.md) - - [Setting up Simulations](./getting_started/setting_up_simulations.md) - # Usage - [Overview](./usage/index.md) - - [Arbiter CLI](./usage/arbiter_cli.md) - [Arbiter Core](./usage/arbiter_core/index.md) - [Environment](./usage/arbiter_core/environment.md) - [Middleware](./usage/arbiter_core/middleware.md) @@ -15,9 +11,9 @@ - [Agents and Engines](./usage/arbiter_engine/agents_and_engines.md) - [Worlds and Universes](./usage/arbiter_engine/worlds_and_universes.md) - [Configuration](./usage/arbiter_engine/configuration.md) + - [Arbiter CLI](./usage/arbiter_cli.md) - [Arbiter Macros](./usage/arbiter_macros.md) - [Techniques](./usage/techniques/index.md) - - [Stateful Testing](./usage/techniques/stateful_testing.md) - [Anomaly Detection](./usage/techniques/anomaly_detection.md) - [Measuring Risk](./usage/techniques/measuring_risk.md) # Engagement diff --git a/documentation/src/auditing/auditing.md b/documentation/src/auditing/auditing.md deleted file mode 100644 index 896c9d3f4..000000000 --- a/documentation/src/auditing/auditing.md +++ /dev/null @@ -1,9 +0,0 @@ -# Auditing - -The current state of software auditing in the EVM is rapidly evolving. Competitive salaries are attracting top talent to firms like [Spearbit](https://spearbit.com/), [ChainSecurity](https://chainsecurity.com/), and [Trail of Bits](https://www.trailofbits.com/), while open security bounties and competitions like [Code Arena](https://code4rena.com/) are drawing in the best and brightest from around the world. Moreover, the rise of decentralized finance and the value at stake in these EVM-oriented systems have also caught the attention of a collection of black hats. - -As competition in auditing intensifies, auditors will likely need to specialize to stay competitive. With its ability to model the EVM with a high degree of granularity, Arbiter is well-positioned to be leveraged by auditors to develop its tooling and methodologies to stay ahead of the curve. - -One such methodology is domain-specific fuzzing. Fuzzing is a testing technique that provides invalid, unexpected, or random data as input to a computer program. The program is then monitored for exceptions such as crashes, failing built-in code assertions, or potential memory leaks. Domain-specific fuzzing in the context of EVM system design involves modeling "normal" system behavior with agents and then playing with different parameters of the system to expose system fragility. - -With its high degree of EVM modeling granularity, Arbiter is well-suited to support and enable domain-specific fuzzing. It can accurately simulate the behavior of the EVM under a wide range of conditions and inputs, providing auditors with a powerful tool for identifying and addressing potential vulnerabilities. Moreover, Arbiter is designed to be highly performant and fast, allowing for efficient and timely auditing processes. This speed and performance make it an even more valuable tool in the rapidly evolving world of software auditing. diff --git a/documentation/src/getting_started/index.md b/documentation/src/getting_started/index.md index 99645a642..3821a5a37 100644 --- a/documentation/src/getting_started/index.md +++ b/documentation/src/getting_started/index.md @@ -6,10 +6,17 @@ The crates (aside from `arbiter-engine` at the moment) are linked to their crate [dependencies] arbiter-core = "*" # You can specify a version here if you'd like arbiter-bindings = "*" # You can specify a version here if you'd like +arbiter-engine = "*" # You can specify a version here if you'd like ``` -## CLI Prerequisites -Before installing Arbiter CLI, ensure that you have Rust installed. You can install and verify your Rust installation from the [official website](https://www.rust-lang.org/tools/install). -The Arbiter CLI works alongside [Foundry](https://github.com/foundry-rs/foundry) and aims to provide a similar CLI interface of setting up and interacting with Arbiter projects. -Install Foundry from the [official website](https://getfoundry.sh/). \ No newline at end of file +# Auditing + +The current state of software auditing in the EVM is rapidly evolving. Competitive salaries are attracting top talent to firms like [Spearbit](https://spearbit.com/), [ChainSecurity](https://chainsecurity.com/), and [Trail of Bits](https://www.trailofbits.com/), while open security bounties and competitions like [Code Arena](https://code4rena.com/) are drawing in the best and brightest from around the world. Moreover, the rise of decentralized finance and the value at stake in these EVM-oriented systems have also caught the attention of a collection of black hats. + +As competition in auditing intensifies, auditors will likely need to specialize to stay competitive. With its ability to model the EVM with a high degree of granularity, Arbiter is well-positioned to be leveraged by auditors to develop its tooling and methodologies to stay ahead of the curve. + +One such methodology is domain-specific fuzzing. Fuzzing is a testing technique that provides invalid, unexpected, or random data as input to a computer program. The program is then monitored for exceptions such as crashes, failing built-in code assertions, or potential memory leaks. Domain-specific fuzzing in the context of EVM system design involves modeling "normal" system behavior with agents and then playing with different parameters of the system to expose system fragility. + +With its high degree of EVM modeling granularity, Arbiter is well-suited to support and enable domain-specific fuzzing. It can accurately simulate the behavior of the EVM under a wide range of conditions and inputs, providing auditors with a powerful tool for identifying and addressing potential vulnerabilities. Moreover, Arbiter is designed to be highly performant and fast, allowing for efficient and timely auditing processes. This speed and performance make it an even more valuable tool in the rapidly evolving world of software auditing. + diff --git a/documentation/src/getting_started/installation.md b/documentation/src/getting_started/installation.md deleted file mode 100644 index cc35e0b55..000000000 --- a/documentation/src/getting_started/installation.md +++ /dev/null @@ -1,22 +0,0 @@ -# Installation - -## Install using Cargo - -Once Rust is installed, you can install Arbiter from the package registry using Cargo. To do this, just run: -```bash -cargo install arbiter -``` - -You can now run `arbiter --version` to verify your installation. - -## Building From Source -Install Git, if you haven't already. There are many online guides on how to install Git on different devices, including one from [Github](https://github.com/git-guides/install-git). - -Once you're done with the above, you can install Arbiter by cloning the repository. The local crate can then be used to install the Arbiter binary on your machine. - -```bash -git clone https://github.com/primitivefinance/arbiter.git -cargo install --path ./arbiter -``` - -You can now run `arbiter --help` to verify your installation and view the help menu. diff --git a/documentation/src/getting_started/setting_up_simulations.md b/documentation/src/getting_started/setting_up_simulations.md deleted file mode 100644 index 2ec682d0f..000000000 --- a/documentation/src/getting_started/setting_up_simulations.md +++ /dev/null @@ -1,48 +0,0 @@ -# Setting up Simulations -Simulation design can be a difficult task but we are developing more tooling to make this process easier. -We will be adding more documentation and tutorials to help you get started with Arbiter and will update this page as we do so. - -## High Level -To begin using Arbiter for simulating your smart contracts, you will want to map out the mechanics of your system. -Some questions you should ask yourself are: -- What are the key mechanisms of my system? -- What agents can interact with this system? -- What are the key objectives of my simulation? - -For example, if you are simulating a decentralized exchange (DEX), you will want to think about the following: -- What are the key mechanisms of my system? - - Liquidity (de)allocations - - Swaps -- What agents can interact with this system? - - Liquidity providers - - Arbitrage bots - - Random swappers -- What are the key objectives of my simulation? - - Identify whether the system is vulnerable to large price movements. - - Identify whether the system is vulnerable to spamming allocations and deallocations over prolonged periods of time. - - Does the DEX perform well financially? - -After reviewing your objectives, consider what new agents you may need to create to simulate attacks or find weaknesses in your system. -Consider adding more objectives and being sure to analyze them scientifically. - - -## Implementation -For you to use Arbiter after mapping out your system, you will need to do the following: -- Identify necessary contracts. -- Create a repository that will hold your simulation code and references to your smart contracts. -- Generate bindings for those contracts. -You can use [`arbiter bind`](../usage/arbiter_cli.md#Bindings) to do this or you can use [`forge bind`](https://book.getfoundry.sh/reference/forge/forge-bind). -Arbiter CLI makes this process a bit easier, so we suggest using it. - -The stage is now set for you to begin writing your simulation code. -This will consist of the following: -- Creating a [`Environment`](../usage/arbiter_core/environment.md) for your simulation. -- Creating agents. -(TODO: More documentation here for [`arbiter-engine`](../usage/arbiter_engine/index.md)) -- Creating a [`RevmMiddleware`](../usage/arbiter_core/middleware.md) for each agent in your simulation. -- Deploy contracts using the binding's `MyContract::deploy()` method which will need a client `Arc` and constructor arguments passed as a tuple. -Or, if you want to use a forked state, use the binding's `MyContract::new()` method and pass it the relevant client and address. - -Once you have deployed your contracts, you can begin interacting with them using the binding's methods. -Your agents can be free to do what they need to do with these contracts to achieve your goals. - diff --git a/documentation/src/usage/arbiter_engine/agents_and_engines.md b/documentation/src/usage/arbiter_engine/agents_and_engines.md index 193f53c41..538e93532 100644 --- a/documentation/src/usage/arbiter_engine/agents_and_engines.md +++ b/documentation/src/usage/arbiter_engine/agents_and_engines.md @@ -4,7 +4,7 @@ The main idea here is that you can have an `Agent` that has as many `Behavior`s This gives flexibility in how you want to design your `Agent`s and what emergent properties you want to observe. ## Design Principles -It is up to you whether or not you prefer to have `Agent`s have multiple `Behavior`s or not or if you want them to have a single `Behavior` that processes all events. +We designed the behaviors to be flexible. It is up to you whether or not you prefer to have `Agent`s have multiple `Behavior`s or not or if you want them to have a single `Behavior` that processes all events. For the former case, you will build `Behavior` for different types `E` and place these inside of an `Agent`. For the latter, you will create an `enum` that wraps all the different types of events that you want to process and then implement `Behavior` on that `enum`. The latter will also require a `stream::select` type of operation to merge all the different event streams into one, though this is not difficult to do. diff --git a/documentation/src/usage/arbiter_engine/behaviors.md b/documentation/src/usage/arbiter_engine/behaviors.md index dd51377e6..0da55c6bc 100644 --- a/documentation/src/usage/arbiter_engine/behaviors.md +++ b/documentation/src/usage/arbiter_engine/behaviors.md @@ -17,7 +17,7 @@ To outline the design principles here: - `startup` is a method that initializes the `Behavior` and returns an `EventStream` that the `Behavior` will use for processing. - This method yields a client and messager from the `Agent` that owns the `Behavior`. In this method you should take the client and messager and store them in your struct if you will need them in the processing of events. - Note, you may not need both or even either! + Note, you may not need them! - `process` is a method that processes an event of type `E` and returns an `Option`. - If `process` returns `Some(MachineHalt)`, then the `Behavior` will stop processing events completely. diff --git a/documentation/src/usage/index.md b/documentation/src/usage/index.md index 0803351fc..d789c6dd7 100644 --- a/documentation/src/usage/index.md +++ b/documentation/src/usage/index.md @@ -1,15 +1,16 @@ -# Usage -Usage of the Arbiter framework is split by each crate. - -## Arbiter CLI -The Arbiter CLI is a minimal interface for managing your Arbiter projects. -It is built on top of Foundry and aims to provide a similar CLI interface of setting up and interacting with Arbiter projects. +# Software Architecture +Arbiter is broken into a number of crates that provide different levels of abstraction for interacting with the Ethereum Virtual Machine (EVM) sandbox. ## Arbiter Core -The `arbiter-core` crate is the core of the Arbiter framework. +The `arbiter-core` crate is the core of the Arbiter. It contains the `Environment` struct which acts as an EVM sandbox and the `RevmMiddleware` which gives a convenient interface for interacting with contracts deployed into the `Environment`. -(TODO) Direct usage of `arbiter-core` will be minimized as much as possible as it is intended for developers to mostly pull from the `arbiter-engine` crate in the future. +Direct usage of `arbiter-core` will be minimized as much as possible as it is intended for developers to mostly pull from the `arbiter-engine` crate in the future. +This crate provides the interface for agents to interact with an in memeory evm. -## Arbiter Engine (TODO) +## Arbiter Engine The `arbiter-engine` crate is the main interface for running simulations. -It is built on top of `arbiter-core` and provides a more ergonomic interface for designing agents and running them in simulations. \ No newline at end of file +It is built on top of `arbiter-core` and provides a more ergonomic interface for designing agents and running them in simulations. + +## Arbiter CLI (under construction) +The Arbiter CLI is a minimal interface for managing your Arbiter projects. +It is built on top of Foundry and aims to provide a similar CLI interface of setting up and interacting with Arbiter projects. \ No newline at end of file diff --git a/documentation/src/usage/techniques/anomaly_detection.md b/documentation/src/usage/techniques/anomaly_detection.md index dfea357cd..d9422bcaf 100644 --- a/documentation/src/usage/techniques/anomaly_detection.md +++ b/documentation/src/usage/techniques/anomaly_detection.md @@ -1 +1,8 @@ # Anomaly Detection + +Anomaly detection is the process of identifying unexpected items or events in data sets, which differ from the norm. +Anomaly detection is often applied on unlabeled data which is known as unsupervised anomaly detection. + +When you are building your simulation you are trying to discover unknown unknowns and carefully examine design assumptions. +This is a difficult task and it is not always clear what you are looking for. +As a result the best place to start is the design a simulation that will validate the existing design assumptions. diff --git a/documentation/src/usage/techniques/index.md b/documentation/src/usage/techniques/index.md index 3ae1529fd..b5764677f 100644 --- a/documentation/src/usage/techniques/index.md +++ b/documentation/src/usage/techniques/index.md @@ -1 +1,8 @@ # Techniques + +At a high level when you are designing a simulation the two things you need to think about are behaviors and one or more random variable. +A random variable is what you can perturb over the course of a simulation. +For example almost all economic models have a random variable that represents the price. +This allows you to see how the model behaves under different prices or market conditions. +Does this system handle price volatility well? +Or does it break down? \ No newline at end of file From cbc87f59d0410bdb623923009ecb38ae745ed06b Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Wed, 7 Feb 2024 12:07:58 -0700 Subject: [PATCH 33/38] add `arbiter-engine` error handling (#852) * cleanup and start errors * saving * save again * cleanup * fixes * version the macros * typos --- Cargo.lock | 1 - arbiter-engine/Cargo.toml | 1 - arbiter-engine/src/agent.rs | 41 ++------- arbiter-engine/src/errors.rs | 45 ++++++++++ .../examples/minter/behaviors/token_admin.rs | 10 +-- .../minter/behaviors/token_requester.rs | 17 ++-- arbiter-engine/src/examples/minter/mod.rs | 2 +- arbiter-engine/src/examples/mod.rs | 3 +- .../src/examples/timed_message/mod.rs | 16 ++-- arbiter-engine/src/lib.rs | 12 ++- arbiter-engine/src/machine.rs | 89 +++++++++++-------- arbiter-engine/src/messager.rs | 61 ++++++++----- arbiter-engine/src/universe.rs | 15 ++-- arbiter-engine/src/world.rs | 86 +++++------------- .../arbiter_engine/agents_and_engines.md | 4 +- .../src/usage/arbiter_engine/behaviors.md | 20 ++--- .../src/usage/arbiter_engine/configuration.md | 6 +- .../arbiter_engine/worlds_and_universes.md | 8 +- 18 files changed, 227 insertions(+), 210 deletions(-) create mode 100644 arbiter-engine/src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index 0d78808a6..7e7f63067 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,7 +291,6 @@ dependencies = [ name = "arbiter-engine" version = "0.1.0" dependencies = [ - "anyhow", "arbiter-bindings", "arbiter-core", "arbiter-macros", diff --git a/arbiter-engine/Cargo.toml b/arbiter-engine/Cargo.toml index 0059563ad..ebb80572b 100644 --- a/arbiter-engine/Cargo.toml +++ b/arbiter-engine/Cargo.toml @@ -23,7 +23,6 @@ serde_json.workspace = true serde.workspace = true tokio.workspace = true async-stream.workspace = true -anyhow = { version = "=1.0.79" } tracing.workspace = true tokio-stream = "0.1.14" futures = "0.3.30" diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index d7330a488..9f5fd59b9 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -1,15 +1,7 @@ //! The agent module contains the core agent abstraction for the Arbiter Engine. -use std::{fmt::Debug, sync::Arc}; - -use arbiter_core::middleware::RevmMiddleware; -use serde::{de::DeserializeOwned, Serialize}; -use thiserror::Error; - -use crate::{ - machine::{Behavior, Engine, StateMachine}, - messager::Messager, -}; +use super::*; +use crate::machine::{Behavior, Engine, StateMachine}; /// An agent is an entity capable of processing events and producing actions. /// These are the core actors in simulations or in onchain systems. @@ -21,6 +13,7 @@ use crate::{ /// each of its [`Behavior`]s `startup()` methods. The [`Behavior`]s themselves /// will return a stream of events that then let the [`Behavior`] move into the /// `State::Processing` stage. +#[derive(Debug)] pub struct Agent { /// Identifier for this agent. /// Used for routing messages. @@ -38,17 +31,6 @@ pub struct Agent { pub(crate) behavior_engines: Vec>, } -impl Debug for Agent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Agent") - .field("id", &self.id) - .field("messager", &self.messager) - .field("client", &self.client) - .field("behavior_engines", &self.behavior_engines) - .finish() - } -} - impl Agent { /// Creates a new [`AgentBuilder`] instance with a specified identifier. /// @@ -119,7 +101,7 @@ impl AgentBuilder { /// # Returns /// /// Returns the `AgentBuilder` instance to allow for method chaining. - pub fn with_engine(mut self, engine: Box) -> Self { + pub(crate) fn with_engine(mut self, engine: Box) -> Self { if let Some(engines) = &mut self.behavior_engines { engines.push(engine); } else { @@ -165,7 +147,7 @@ impl AgentBuilder { self, client: Arc, messager: Messager, - ) -> Result { + ) -> Result { match self.behavior_engines { Some(engines) => Ok(Agent { id: self.id, @@ -173,16 +155,9 @@ impl AgentBuilder { client, behavior_engines: engines, }), - None => Err(AgentBuildError::MissingBehaviorEngines), + None => Err(ArbiterEngineError::AgentBuildError( + "Missing behavior engines".to_owned(), + )), } } } - -/// enum representing the possible error states encountered by the agent builder -#[derive(Debug, Error, Clone, PartialEq, Eq)] -pub enum AgentBuildError { - /// Error representing the case where the agent is missing behavior engines; - /// an agent has to have behaviors to be useful! - #[error("Agent is missing behavior engines")] - MissingBehaviorEngines, -} diff --git a/arbiter-engine/src/errors.rs b/arbiter-engine/src/errors.rs new file mode 100644 index 000000000..8dcdce640 --- /dev/null +++ b/arbiter-engine/src/errors.rs @@ -0,0 +1,45 @@ +//! Error types for the arbiter engine. + +use thiserror::Error; + +use super::*; + +/// Errors that can occur in the arbiter engine. +#[derive(Debug, Error)] +pub enum ArbiterEngineError { + /// Error occurred with the [`Messager`]. + #[error("MessagerError: {0}")] + MessagerError(String), + + /// Error occurred with the [`crate::agent::Agent`]. + #[error("AgentBuildError: {0}")] + AgentBuildError(String), + + /// Error occurred with the [`crate::world::World`]. + #[error("WorldError: {0}")] + WorldError(String), + + /// Error occurred with the [`crate::universe::Universe`]. + #[error("UniverseError: {0}")] + UniverseError(String), + + /// Error occurred in joining a task. + #[error(transparent)] + JoinError(#[from] tokio::task::JoinError), + + /// Error occurred in sending a message. + #[error(transparent)] + SendError(#[from] tokio::sync::broadcast::error::SendError), + + /// Error occurred in deserializing json. + #[error(transparent)] + SerdeJsonError(#[from] serde_json::Error), + + /// Error occurred in reading in a file. + #[error(transparent)] + IoError(#[from] std::io::Error), + + /// Error occurred in deserializing toml. + #[error(transparent)] + TomlError(#[from] toml::de::Error), +} diff --git a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs index 59d022712..f4d968988 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs @@ -31,7 +31,7 @@ impl Behavior for TokenAdmin { &mut self, client: Arc, messager: Messager, - ) -> EventStream { + ) -> Result, ArbiterEngineError> { self.messager = Some(messager.clone()); self.client = Some(client.clone()); for token_data in self.token_data.values_mut() { @@ -53,12 +53,12 @@ impl Behavior for TokenAdmin { .get_or_insert_with(HashMap::new) .insert(token_data.name.clone(), token.clone()); } - Box::pin(messager.stream()) + messager.stream() } #[tracing::instrument(skip(self), fields(id = self.messager.as_ref().unwrap().id.as_deref()))] - async fn process(&mut self, event: Message) -> Option { + async fn process(&mut self, event: Message) -> Result { if self.tokens.is_none() { error!( "There were no tokens to deploy! You must add tokens to @@ -98,10 +98,10 @@ impl Behavior for TokenAdmin { self.count += 1; if self.count == self.max_count.unwrap_or(u64::MAX) { warn!("Reached max count. Halting behavior."); - return Some(MachineHalt); + return Ok(ControlFlow::Halt); } } } - None + Ok(ControlFlow::Continue) } } diff --git a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs index c662d3969..1616905b8 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs @@ -2,7 +2,10 @@ use arbiter_bindings::bindings::arbiter_token::TransferFilter; use arbiter_core::data_collection::EventLogger; use token_admin::{MintRequest, TokenAdminQuery}; -use self::{examples::minter::agents::token_requester::TokenRequester, machine::EventStream}; +use self::{ + errors::ArbiterEngineError, examples::minter::agents::token_requester::TokenRequester, + machine::EventStream, +}; use super::*; #[async_trait::async_trait] @@ -12,14 +15,14 @@ impl Behavior for TokenRequester { &mut self, client: Arc, mut messager: Messager, - ) -> EventStream { + ) -> Result, ArbiterEngineError> { messager .send( To::Agent(self.request_to.clone()), &TokenAdminQuery::AddressOf(self.token_data.name.clone()), ) .await; - let message = messager.get_next().await; + let message = messager.get_next().await.unwrap(); let token_address = serde_json::from_str::
(&message.data).unwrap(); let token = ArbiterToken::new(token_address, client.clone()); self.token_data.address = Some(token_address); @@ -35,18 +38,18 @@ impl Behavior for TokenRequester { self.messager = Some(messager.clone()); self.client = Some(client.clone()); - return Box::pin( + Ok(Box::pin( EventLogger::builder() .add_stream(token.transfer_filter()) .stream() .unwrap() .map(|value| serde_json::from_str(&value).unwrap()), - ); + )) } #[tracing::instrument(skip(self), fields(id = self.messager.as_ref().unwrap().id.as_deref()))] - async fn process(&mut self, event: TransferFilter) -> Option { + async fn process(&mut self, event: TransferFilter) -> Result { let messager = self.messager.as_ref().unwrap(); while (self.count < self.max_count.unwrap()) { debug!("sending message from requester"); @@ -60,6 +63,6 @@ impl Behavior for TokenRequester { .await; self.count += 1; } - Some(MachineHalt) + Ok(ControlFlow::Halt) } } diff --git a/arbiter-engine/src/examples/minter/mod.rs b/arbiter-engine/src/examples/minter/mod.rs index ba5ec2e32..b6bf9e336 100644 --- a/arbiter-engine/src/examples/minter/mod.rs +++ b/arbiter-engine/src/examples/minter/mod.rs @@ -14,7 +14,7 @@ use tracing::error; use super::*; use crate::{ agent::Agent, - machine::{Behavior, MachineHalt, MachineInstruction, StateMachine}, + machine::{Behavior, ControlFlow, MachineInstruction, StateMachine}, messager::To, world::World, }; diff --git a/arbiter-engine/src/examples/mod.rs b/arbiter-engine/src/examples/mod.rs index 592e9e647..63147553c 100644 --- a/arbiter-engine/src/examples/mod.rs +++ b/arbiter-engine/src/examples/mod.rs @@ -11,8 +11,9 @@ use futures_util::{stream, StreamExt}; use super::*; use crate::{ agent::Agent, + errors::ArbiterEngineError, machine::{ - Behavior, CreateStateMachine, Engine, EventStream, MachineHalt, State, StateMachine, + Behavior, ControlFlow, CreateStateMachine, Engine, EventStream, State, StateMachine, }, messager::{Message, Messager, To}, world::World, diff --git a/arbiter-engine/src/examples/timed_message/mod.rs b/arbiter-engine/src/examples/timed_message/mod.rs index 86da65228..ed1cb324f 100644 --- a/arbiter-engine/src/examples/timed_message/mod.rs +++ b/arbiter-engine/src/examples/timed_message/mod.rs @@ -56,24 +56,24 @@ impl Behavior for TimedMessage { &mut self, _client: Arc, messager: Messager, - ) -> EventStream { + ) -> Result, ArbiterEngineError> { if let Some(startup_message) = &self.startup_message { messager.send(To::All, startup_message).await; } self.messager = Some(messager.clone()); - return messager.stream(); + messager.stream() } - async fn process(&mut self, event: Message) -> Option { + async fn process(&mut self, event: Message) -> Result { if event.data == serde_json::to_string(&self.receive_data).unwrap() { let messager = self.messager.clone().unwrap(); messager.send(To::All, self.send_data.clone()).await; self.count += 1; } if self.count == self.max_count.unwrap_or(u64::MAX) { - return Some(MachineHalt); + return Ok(ControlFlow::Halt); } - return None; + Ok(ControlFlow::Continue) } } @@ -94,7 +94,7 @@ async fn echoer() { world.run().await; - let mut stream = Box::pin(messager.stream()); + let mut stream = messager.stream().unwrap(); let mut idx = 0; loop { @@ -136,7 +136,7 @@ async fn ping_pong() { let messager = world.messager.for_agent("outside_world"); world.run().await; - let mut stream = Box::pin(messager.stream()); + let mut stream = messager.stream().unwrap(); let mut idx = 0; loop { @@ -177,7 +177,7 @@ async fn ping_pong_two_agent() { let messager = world.messager.for_agent("outside_world"); world.run().await; - let mut stream = Box::pin(messager.stream()); + let mut stream = messager.stream().unwrap(); let mut idx = 0; loop { diff --git a/arbiter-engine/src/lib.rs b/arbiter-engine/src/lib.rs index c85ee2f15..b304a573b 100644 --- a/arbiter-engine/src/lib.rs +++ b/arbiter-engine/src/lib.rs @@ -5,14 +5,18 @@ //! distributed fashion where each agent is running in its own process and //! communicating with other agents via a messaging layer. -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Debug, sync::Arc}; -use anyhow::{anyhow, Result}; -use serde::{Deserialize, Serialize}; -#[allow(unused)] +use arbiter_core::middleware::RevmMiddleware; +use futures_util::future::join_all; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use tokio::task::{spawn, JoinError}; use tracing::{debug, trace, warn}; +use crate::{errors::ArbiterEngineError, messager::Messager}; + pub mod agent; +pub mod errors; pub mod examples; pub mod machine; pub mod messager; diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index b8d8071e9..5c76ddcf2 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -1,13 +1,11 @@ //! The [`StateMachine`] trait, [`Behavior`] trait, and the [`Engine`] that runs //! [`Behavior`]s. -use std::{fmt::Debug, pin::Pin, sync::Arc}; +use std::pin::Pin; -use arbiter_core::middleware::RevmMiddleware; use futures_util::{Stream, StreamExt}; -use serde::de::DeserializeOwned; +use tokio::task::JoinHandle; -use self::messager::Messager; use super::*; /// A type alias for a pinned, boxed stream of events. @@ -29,15 +27,21 @@ pub enum MachineInstruction { /// Used to make a [`StateMachine`] process events. /// This will offload the process into a task that can be halted by sending - /// a [`MachineHalt`] message from the [`Messager`]. For our purposes, the - /// [`crate::world::World`] will handle this. + /// a [`ControlFlow::Halt`] message from the [`Messager`]. For our purposes, + /// the [`crate::world::World`] will handle this. Process, } -/// The message that can be used in a [`StateMachine`] to halt its processing. -/// Optionally returned by [`Behavior::process`] to close tasks. +/// The message that is used in a [`StateMachine`] to continue or halt its +/// processing. #[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct MachineHalt; +pub enum ControlFlow { + /// Used to halt the processing of a [`StateMachine`]. + Halt, + + /// Used to continue on the processing of a [`StateMachine`]. + Continue, +} /// The state used by any entity implementing [`StateMachine`]. #[derive(Clone, Copy, Debug)] @@ -68,12 +72,16 @@ pub trait Behavior: Serialize + DeserializeOwned + Send + Sync + Debug + 'sta /// Used to start the agent. /// This is where the agent can engage in its specific start up activities /// that it can do given the current state of the world. - async fn startup(&mut self, client: Arc, messager: Messager) -> EventStream; + async fn startup( + &mut self, + client: Arc, + messager: Messager, + ) -> Result, ArbiterEngineError>; /// Used to process events. /// This is where the agent can engage in its specific processing /// of events that can lead to actions being taken. - async fn process(&mut self, event: E) -> Option; + async fn process(&mut self, event: E) -> Result; } /// A trait for creating a state machine. /// @@ -130,7 +138,7 @@ pub trait StateMachine: Send + Sync + Debug + 'static { /// This method does not return a value, but it may result in state changes /// within the implementing type or the generation of further instructions /// or events. - async fn execute(&mut self, instruction: MachineInstruction); + async fn execute(&mut self, instruction: MachineInstruction) -> Result<(), ArbiterEngineError>; } /// The `Engine` struct represents the core logic unit of a state machine-based @@ -148,15 +156,15 @@ pub trait StateMachine: Send + Sync + Debug + 'static { /// - `behavior`: An optional behavior that the engine is currently managing. /// This is where the engine's logic is primarily executed in response to /// events. -pub struct Engine +pub(crate) struct Engine where B: Behavior, { /// The behavior the `Engine` runs. - pub behavior: Option, + behavior: Option, /// The current state of the [`Engine`]. - pub state: State, + state: State, /// The receiver of events that the [`Engine`] will process. /// The [`State::Processing`] stage will attempt a decode of the [`String`]s @@ -201,39 +209,46 @@ where B: Behavior + Debug + Serialize + DeserializeOwned, E: DeserializeOwned + Serialize + Send + Sync + Debug + 'static, { - async fn execute(&mut self, instruction: MachineInstruction) { + async fn execute(&mut self, instruction: MachineInstruction) -> Result<(), ArbiterEngineError> { + // NOTE: The unwraps here are safe because the `Behavior` in an engine is only + // accessed here and it is private. match instruction { MachineInstruction::Start(client, messager) => { - trace!("Behavior is starting up."); self.state = State::Starting; let mut behavior = self.behavior.take().unwrap(); - let behavior_task = tokio::spawn(async move { - let id = messager.id.clone(); - let stream = behavior.startup(client, messager).await; - debug!("startup complete for {:?}!", id); - (stream, behavior) - }); - let (stream, behavior) = behavior_task.await.unwrap(); + let behavior_task: JoinHandle, B), ArbiterEngineError>> = + tokio::spawn(async move { + let id = messager.id.clone(); + let stream = behavior.startup(client, messager).await?; + debug!("startup complete for behavior {:?}", id); + Ok((stream, behavior)) + }); + let (stream, behavior) = behavior_task.await??; self.event_stream = Some(stream); self.behavior = Some(behavior); - // TODO: This feels weird but I think it works properly? - self.execute(MachineInstruction::Process).await; + self.execute(MachineInstruction::Process).await?; + Ok(()) } MachineInstruction::Process => { + trace!("Behavior is starting up."); let mut behavior = self.behavior.take().unwrap(); let mut stream = self.event_stream.take().unwrap(); - let behavior_task = tokio::spawn(async move { - while let Some(event) = stream.next().await { - let halt_option = behavior.process(event).await; - if halt_option.is_some() { - break; + let behavior_task: JoinHandle> = + tokio::spawn(async move { + while let Some(event) = stream.next().await { + match behavior.process(event).await? { + ControlFlow::Halt => { + break; + } + ControlFlow::Continue => {} + } } - } - behavior - }); - // TODO: This could be removed as we probably don't need to have the behavior - // stored once its done. - self.behavior = Some(behavior_task.await.unwrap()); + Ok(behavior) + }); + // TODO: We don't have to store the behavior again here, we could just discard + // it. + self.behavior = Some(behavior_task.await??); + Ok(()) } } } diff --git a/arbiter-engine/src/messager.rs b/arbiter-engine/src/messager.rs index 7f4032ac5..6d95b4a00 100644 --- a/arbiter-engine/src/messager.rs +++ b/arbiter-engine/src/messager.rs @@ -1,14 +1,9 @@ //! The messager module contains the core messager layer for the Arbiter Engine. -// TODO: Allow for modulating the capacity of the messager. -// TODO: It might be nice to have some kind of messaging header so that we can -// pipe messages to agents and pipe messages across worlds. - -use serde::Serialize; use tokio::sync::broadcast::{channel, Receiver, Sender}; -use self::machine::EventStream; use super::*; +use crate::machine::EventStream; /// A message that can be sent between agents. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -79,16 +74,25 @@ impl Messager { /// utility function for getting the next value from the broadcast_receiver /// without streaming - pub async fn get_next(&mut self) -> Message { - while let Ok(message) = self.broadcast_receiver.as_mut().unwrap().recv().await { + pub async fn get_next(&mut self) -> Result { + let mut receiver = match self.broadcast_receiver.take() { + Some(receiver) => receiver, + None => { + return Err(ArbiterEngineError::MessagerError( + "Receiver has been taken! Are you already streaming on this messager?" + .to_owned(), + )) + } + }; + while let Ok(message) = receiver.recv().await { match &message.to { To::All => { - return message; + return Ok(message); } To::Agent(id) => { if let Some(self_id) = &self.id { if id == self_id { - return message; + return Ok(message); } } continue; @@ -100,9 +104,17 @@ impl Messager { /// Returns a stream of messages that are either sent to [`To::All`] or to /// the agent via [`To::Agent(id)`]. - pub fn stream(mut self) -> EventStream { - let mut receiver = self.broadcast_receiver.take().unwrap(); - Box::pin(async_stream::stream! { + pub fn stream(mut self) -> Result, ArbiterEngineError> { + let mut receiver = match self.broadcast_receiver.take() { + Some(receiver) => receiver, + None => { + return Err(ArbiterEngineError::MessagerError( + "Receiver has been taken! Are you already streaming on this messager?" + .to_owned(), + )) + } + }; + Ok(Box::pin(async_stream::stream! { while let Ok(message) = receiver.recv().await { match &message.to { To::All => { @@ -117,7 +129,7 @@ impl Messager { } } } - }) + })) } /// Asynchronously sends a message to a specified recipient. /// @@ -138,13 +150,20 @@ impl Messager { /// a broadcast to all agents. /// - `data`: The data to be sent in the message. This data is serialized /// into JSON format. - pub async fn send(&self, to: To, data: S) { + pub async fn send(&self, to: To, data: S) -> Result<(), ArbiterEngineError> { trace!("Sending message via messager."); - let message = Message { - from: self.id.clone().unwrap(), - to, - data: serde_json::to_string(&data).unwrap(), - }; - self.broadcast_sender.send(message).unwrap(); + if let Some(id) = &self.id { + let message = Message { + from: id.clone(), + to, + data: serde_json::to_string(&data)?, + }; + self.broadcast_sender.send(message)?; + Ok(()) + } else { + Err(ArbiterEngineError::MessagerError( + "Messager has no ID! You must have an ID to send messages!".to_owned(), + )) + } } } diff --git a/arbiter-engine/src/universe.rs b/arbiter-engine/src/universe.rs index 66154dff6..0a9671837 100644 --- a/arbiter-engine/src/universe.rs +++ b/arbiter-engine/src/universe.rs @@ -1,12 +1,7 @@ //! The [`universe`] module contains the [`Universe`] struct which is the //! primary interface for creating and running many `World`s in parallel. -use std::collections::HashMap; - -use anyhow::Result; -use futures_util::future::join_all; -use tokio::task::{spawn, JoinError}; - +use super::*; use crate::world::World; /// The [`Universe`] struct is the primary interface for creating and running @@ -36,12 +31,14 @@ impl Universe { } /// Runs all of the [`World`]s in the [`Universe`] in parallel. - pub async fn run_worlds(&mut self) -> Result<()> { + pub async fn run_worlds(&mut self) -> Result<(), ArbiterEngineError> { if self.is_online() { - return Err(anyhow::anyhow!("Universe is already running.")); + return Err(ArbiterEngineError::UniverseError( + "Universe is already running.".to_owned(), + )); } let mut tasks = Vec::new(); - // TODO: These unwraps need to be checkdd a bit. + // NOTE: Unwrap is safe because we checked if the universe is online. for (_, mut world) in self.worlds.take().unwrap().drain() { tasks.push(spawn(async move { world.run().await.unwrap(); diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 53981cdcf..9dca5211f 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -1,30 +1,14 @@ -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// TODO: Notes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// * Probably should move labels to world instead of on the environment. -// * One thing that is different about the Arbiter world is that give a bunch of -// different channels to communicate with the Environment's tx thread. This is -// different from a connection to a blockchain where you typically will just -// have a single HTTP/WS connection. What we want is some kind of way of -// having the world own a reference to a provider or something -// * Can add a messager as an interconnect and have the manager give each world -// it owns a clone of the same messager. -// * The worlds now are just going to be revm worlds. We can generalize this -// later. -// * Can we give the world an address book?? -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - //! The world module contains the core world abstraction for the Arbiter Engine. -use std::{collections::VecDeque, fmt::Debug}; +use std::collections::VecDeque; -use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; -use futures_util::future::join_all; -use serde::de::DeserializeOwned; -use tokio::spawn; +use arbiter_core::environment::Environment; -use self::{agent::AgentBuilder, machine::MachineInstruction}; use super::*; -use crate::{agent::Agent, machine::CreateStateMachine, messager::Messager}; +use crate::{ + agent::{Agent, AgentBuilder}, + machine::{CreateStateMachine, MachineInstruction}, +}; /// A world is a collection of agents that use the same type of provider, e.g., /// operate on the same blockchain or same `Environment`. The world is @@ -113,26 +97,26 @@ impl World { pub fn from_config( &mut self, config_path: &str, - ) { - let cwd = std::env::current_dir().expect("Failed to determine current working directory"); + ) -> Result<(), ArbiterEngineError> { + let cwd = std::env::current_dir()?; let path = cwd.join(config_path); - let mut file = File::open(path).expect("Failed to open configuration file"); + let mut file = File::open(path)?; let mut contents = String::new(); - file.read_to_string(&mut contents) - .expect("Failed to read configuration file to string"); + file.read_to_string(&mut contents)?; - let agents_map: HashMap> = - toml::from_str(&contents).expect("Failed to deserialize configuration file"); + let agents_map: HashMap> = toml::from_str(&contents)?; for (agent, behaviors) in agents_map { let mut next_agent = Agent::builder(&agent); for behavior in behaviors { + println!("Behavior: {:?}", behavior); let engine = behavior.create_state_machine(); next_agent = next_agent.with_engine(engine); } self.add_agent(next_agent); } + Ok(()) } /// Adds an agent, constructed from the provided `AgentBuilder`, to the @@ -190,13 +174,16 @@ impl World { /// Returns an error if no agents are found in the world, possibly /// indicating that the world has already been run or that no agents /// were added prior to execution. - pub async fn run(&mut self) -> Result<()> { + pub async fn run(&mut self) -> Result<(), ArbiterEngineError> { + let agents = match self.agents.take() { + Some(agents) => agents, + None => { + return Err(ArbiterEngineError::WorldError( + "No agents found. Has the world already been ran?".to_owned(), + )) + } + }; let mut tasks = vec![]; - // Retrieve the agents, erroring if none are found. - let agents = self - .agents - .take() - .ok_or_else(|| anyhow!("No agents found! Has the world already been run?"))?; // Prepare a queue for messagers corresponding to each behavior engine. let mut messagers = VecDeque::new(); // Populate the messagers queue. @@ -206,6 +193,7 @@ impl World { } } // For each agent, spawn a task for each of its behavior engines. + // Unwrap here is safe as we just built the dang thing. for (_, mut agent) in agents { for mut engine in agent.behavior_engines.drain(..) { let client = agent.client.clone(); @@ -222,31 +210,3 @@ impl World { Ok(()) } } - -#[cfg(test)] -mod tests { - use std::{str::FromStr, sync::Arc}; - - use arbiter_bindings::bindings::weth::WETH; - use ethers::{ - providers::{Middleware, Provider, Ws}, - types::Address, - }; - use futures_util::StreamExt; - - #[ignore = "This is unnecessary to run on CI currently."] - #[tokio::test] - async fn mainnet_ws() { - let ws_url = std::env::var("MAINNET_WS_URL").expect("MAINNET_WS_URL must be set"); - let ws = Ws::connect(ws_url).await.unwrap(); - let provider = Provider::new(ws); - let client = Arc::new(provider); - let weth = WETH::new( - Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(), - client.clone(), - ); - let filter = weth.approval_filter().filter; - let mut subscription = client.subscribe_logs(&filter).await.unwrap(); - println!("next: {:?}", subscription.next().await); - } -} diff --git a/documentation/src/usage/arbiter_engine/agents_and_engines.md b/documentation/src/usage/arbiter_engine/agents_and_engines.md index 538e93532..e477c8ecb 100644 --- a/documentation/src/usage/arbiter_engine/agents_and_engines.md +++ b/documentation/src/usage/arbiter_engine/agents_and_engines.md @@ -13,7 +13,7 @@ The latter will also require a `stream::select` type of operation to merge all t The `Agent` struct is the primary struct that you will be working with. It contains an ID, a client (`Arc`) that provides means to send calls and transactions to an Arbiter `Environment`, and a `Messager`. It looks like this: -```rust +```rust, ignore pub struct Agent { pub id: String, pub messager: Messager, @@ -39,7 +39,7 @@ This encapsulation also allows the `Agent` to hold onto `Behavior` for variou ## Example Let's create an `Agent` that has two `Behavior`s using the `Replier` behavior from before. -```rust +```rust, ignore use arbiter_engine::agent::Agent; use crate::Replier; diff --git a/documentation/src/usage/arbiter_engine/behaviors.md b/documentation/src/usage/arbiter_engine/behaviors.md index 0da55c6bc..ff2c0ce9b 100644 --- a/documentation/src/usage/arbiter_engine/behaviors.md +++ b/documentation/src/usage/arbiter_engine/behaviors.md @@ -7,10 +7,10 @@ All you should be looking for is how to define your `Agent`s behaviors and what ## `trait Behavior` To define a `Behavior`, you need to implement the `Behavior` trait on a struct of your own design. The `Behavior` trait is defined as follows: -```rust +```rust, ignore pub trait Behavior { - fn startup(&mut self, client: Arc, messager: Messager) -> EventStream; - fn process(&mut self, event: E) -> Option; + fn startup(&mut self, client: Arc, messager: Messager) -> Result, ArbiterEngineError>; + fn process(&mut self, event: E) -> Result; } ``` To outline the design principles here: @@ -28,11 +28,11 @@ Otherwise you risk having a `Behavior` that is too complex and difficult to unde ### Example To see this in use, let's take a look at an example of a `Behavior` called `Replier` that replies to a message with a message of its own, and stops once it has replied a certain number of times. -```rust +```rust, ignore use std::sync::Arc; use arbiter_core::middleware::RevmMiddleware; use arbiter_engine::{ - machine::{Behavior, MachineHalt}, + machine::{Behavior, ControlFlow}, messager::{Messager, To}, EventStream}; @@ -68,23 +68,23 @@ impl Behavior for Replier { &mut self, client: Arc, messager: Messager, - ) -> EventStream { + ) -> Result, ArbiterEngineError> { if let Some(startup_message) = &self.startup_message { messager.send(To::All, startup_message).await; } self.messager = Some(messager.clone()); - return messager.stream(); + messager.stream() } - async fn process(&mut self, event: Message) -> Option { + async fn process(&mut self, event: Message) -> Result { if event.data == self.receive_data { self.messager.unwrap().messager.send(To::All, send_data).await; self.count += 1; } if self.count == self.max_count { - return Some(MachineHalt); + return Ok(ControlFlow::Halt); } - return None + Ok(ControlFlow::Continue) } } ``` diff --git a/documentation/src/usage/arbiter_engine/configuration.md b/documentation/src/usage/arbiter_engine/configuration.md index 071145db1..19b5873de 100644 --- a/documentation/src/usage/arbiter_engine/configuration.md +++ b/documentation/src/usage/arbiter_engine/configuration.md @@ -6,7 +6,7 @@ Let's take a look at how to do this. It is good practice to take your `Behavior`s and wrap them in an `enum` so that you can use them in a configuration file. For instance, let's say you have two struct `Maker` and `Taker` that implement `Behavior` for their own `E`. Then you can make your `enum` like this: -```rust +```rust, ignore use arbiter_macros::Behaviors; #[derive(Behaviors)] @@ -23,7 +23,7 @@ The macro solely requires that the `Behavior`s you have implement the `Behavior` Now that you have your `enum` of `Behavior`s, you can configure your `World` and the `Agent`s inside of it from configuration file. Since the `World` and your simulation is completely defined by the `Agent` `Behavior`s you make, all you need to do is specify your `Agent`s in the configuration file. For example, let's say we have the `Replier` behavior from before, so we have: -```rust +```rust, ignore #[derive(Behaviors)] pub enum Behaviors { Replier(Replier), @@ -57,7 +57,7 @@ Replier = { send_data = "pong", receive_data = "ping", max_count = 5 } ## Loading the Configuration Once you have your configuration file located at `./path/to/config.toml`, you can load it and run your simulation like this: -```rust +```rust, ignore fn main() { let world = World::from_config("./path/to/config.toml")?; world.run().await; diff --git a/documentation/src/usage/arbiter_engine/worlds_and_universes.md b/documentation/src/usage/arbiter_engine/worlds_and_universes.md index 1f6315e64..1fd8f25ad 100644 --- a/documentation/src/usage/arbiter_engine/worlds_and_universes.md +++ b/documentation/src/usage/arbiter_engine/worlds_and_universes.md @@ -10,7 +10,7 @@ The choice is yours. ## `struct Universe` The `Universe` struct looks like this: -```rust +```rust, ignore pub struct Universe { worlds: Option>, world_tasks: Option>>, @@ -24,7 +24,7 @@ The `Universe::run_worlds` currently iterates through the `World`s and starts th ## `struct World` The `World` struct looks like this: -```rust +```rust, ignore pub struct World { pub id: String, pub agents: Option>, @@ -42,7 +42,7 @@ In future development, the `World` will be generic over your choice of `Provider ## Example Let's first do a quick example where we take a `World` and add an `Agent` to it. -```rust +```rust, ignore use arbiter_engine::{agent::Agent, world::World}; use crate::Replier; @@ -62,7 +62,7 @@ async fn run() { } ``` If you wanted to extend this to use a `Universe`, you would simply create a `Universe` and add the `World` to it. -```rust +```rust, ignore use arbiter_engine::{agent::Agent, world::World}; use crate::Replier; From c82d56e727ca04705d24628060acfc10e72f30dc Mon Sep 17 00:00:00 2001 From: Colin Roberts Date: Thu, 8 Feb 2024 09:03:12 -0700 Subject: [PATCH 34/38] refactor arbiter-core (#858) * large scale refactor * chore: error consolidation * chore: remove unused imports * chore: remove errors * updating to revm 4.0.0 * error refactor * fix tests and clean up imports * saving progress * cleanup * cleanup * Update lib.rs --------- Co-authored-by: Waylon Jepsen Co-authored-by: Waylon Jepsen <57912727+0xJepsen@users.noreply.github.com> --- Cargo.lock | 176 ++-- Cargo.toml | 14 +- arbiter-core/Cargo.toml | 16 +- arbiter-core/benches/bench.rs | 6 +- arbiter-core/src/console/mod.rs | 13 +- arbiter-core/src/coprocessor.rs | 47 +- arbiter-core/src/data_collection.rs | 25 +- .../src/{environment => database}/fork.rs | 22 +- arbiter-core/src/{ => database}/inspector.rs | 43 +- .../src/{database.rs => database/mod.rs} | 27 +- arbiter-core/src/environment/cheatcodes.rs | 85 -- arbiter-core/src/environment/errors.rs | 73 -- arbiter-core/src/environment/instruction.rs | 109 ++- arbiter-core/src/environment/mod.rs | 520 +++++------ arbiter-core/src/environment/tests.rs | 37 - arbiter-core/src/errors.rs | 122 +++ arbiter-core/src/lib.rs | 26 +- arbiter-core/src/middleware/cast.rs | 53 -- arbiter-core/src/middleware/connection.rs | 57 +- arbiter-core/src/middleware/errors.rs | 88 -- arbiter-core/src/middleware/mod.rs | 807 ++++++++---------- .../src/middleware/nonce_middleware.rs | 12 +- arbiter-core/src/middleware/transaction.rs | 55 -- .../{src/tests/mod.rs => tests/common.rs} | 46 +- arbiter-core/{src => }/tests/contracts.rs | 9 +- .../tests/data_collection_integration.rs | 27 +- .../tests/environment_integration.rs | 30 +- .../{src => }/tests/middleware_integration.rs | 54 +- arbiter-engine/src/agent.rs | 16 +- .../src/examples/minter/agents/token_admin.rs | 4 +- .../examples/minter/agents/token_requester.rs | 2 +- .../examples/minter/behaviors/token_admin.rs | 2 +- .../minter/behaviors/token_requester.rs | 2 +- arbiter-engine/src/examples/minter/mod.rs | 2 +- arbiter-engine/src/examples/mod.rs | 2 +- .../src/examples/timed_message/mod.rs | 2 +- arbiter-engine/src/machine.rs | 7 +- arbiter-engine/src/world.rs | 7 +- bin/fork/mod.rs | 2 +- bin/fork/tests.rs | 2 +- .../src/usage/arbiter_core/middleware.md | 28 +- 41 files changed, 1110 insertions(+), 1567 deletions(-) rename arbiter-core/src/{environment => database}/fork.rs (83%) rename arbiter-core/src/{ => database}/inspector.rs (70%) rename arbiter-core/src/{database.rs => database/mod.rs} (92%) delete mode 100644 arbiter-core/src/environment/cheatcodes.rs delete mode 100644 arbiter-core/src/environment/errors.rs delete mode 100644 arbiter-core/src/environment/tests.rs create mode 100644 arbiter-core/src/errors.rs delete mode 100644 arbiter-core/src/middleware/cast.rs delete mode 100644 arbiter-core/src/middleware/errors.rs delete mode 100644 arbiter-core/src/middleware/transaction.rs rename arbiter-core/{src/tests/mod.rs => tests/common.rs} (64%) rename arbiter-core/{src => }/tests/contracts.rs (98%) rename arbiter-core/{src => }/tests/data_collection_integration.rs (86%) rename arbiter-core/{src => }/tests/environment_integration.rs (86%) rename arbiter-core/{src => }/tests/middleware_integration.rs (92%) diff --git a/Cargo.lock b/Cargo.lock index 7e7f63067..5f4647d04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c234f92024707f224510ff82419b2be0e1d8e1fd911defcac5a085cd7f83898" +checksum = "f4b6fb2b432ff223d513db7f908937f63c252bee0af9b82bfd25b0a5dd1eb0d8" dependencies = [ "alloy-rlp", "bytes", @@ -111,6 +111,7 @@ dependencies = [ "derive_more", "hex-literal", "itoa", + "k256", "keccak-asm", "proptest", "rand", @@ -233,14 +234,14 @@ dependencies = [ "proc-macro2", "rayon", "revm", - "revm-primitives 1.3.0 (git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858)", + "revm-primitives 2.0.0", "serde", "serde_json", "syn 2.0.48", "tempfile", "thiserror", "tokio", - "toml 0.8.9", + "toml 0.8.10", ] [[package]] @@ -275,7 +276,7 @@ dependencies = [ "rand", "rand_distr", "revm", - "revm-primitives 1.3.0 (git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858)", + "revm-primitives 2.0.0", "serde", "serde_json", "statrs", @@ -285,6 +286,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-test", + "uint", ] [[package]] @@ -305,7 +307,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "toml 0.8.9", + "toml 0.8.10", "tracing", "tracing-subscriber", "tracing-test", @@ -644,29 +646,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" -[[package]] -name = "bindgen" -version = "0.66.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" -dependencies = [ - "bitflags 2.4.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.48", - "which", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -855,11 +834,10 @@ dependencies = [ [[package]] name = "c-kzg" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32700dc7904064bb64e857d38a1766607372928e2466ee5f02a869829b3297d7" +checksum = "b9d8c306be83ec04bf5f73710badd8edf56dea23f2f0d8b7f9fe4644d371c758" dependencies = [ - "bindgen", "blst", "cc", "glob", @@ -923,15 +901,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -962,17 +931,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clang-sys" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.4.18" @@ -1099,7 +1057,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml 0.8.9", + "toml 0.8.10", "yaml-rust", ] @@ -1729,7 +1687,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.48", - "toml 0.8.9", + "toml 0.8.10", "walkdir", ] @@ -1978,7 +1936,7 @@ dependencies = [ "atomic", "pear", "serde", - "toml 0.8.9", + "toml 0.8.10", "uncased", "version_check", ] @@ -2061,7 +2019,7 @@ dependencies = [ "path-slash", "regex", "reqwest", - "revm-primitives 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "revm-primitives 1.3.0", "semver 1.0.20", "serde", "serde_json", @@ -2721,12 +2679,6 @@ dependencies = [ "spin 0.5.2", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lexical-core" version = "0.8.5" @@ -2797,16 +2749,6 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "libloading" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "libm" version = "0.2.8" @@ -3358,12 +3300,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pem" version = "1.1.1" @@ -4285,10 +4221,12 @@ dependencies = [ [[package]] name = "revm" -version = "3.5.0" -source = "git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858#30bbcdfe81446c9d1e9b37acc95f208943ddf858" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7e55c6c96e1fd215dc981978602e6b5943b48509267060fb8e73714b44a47a8" dependencies = [ "auto_impl", + "cfg-if", "ethers-core", "ethers-providers", "futures", @@ -4301,23 +4239,26 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "1.3.0" -source = "git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858#30bbcdfe81446c9d1e9b37acc95f208943ddf858" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d7aac43d36bcd69ba4ad53aeedc0deaf411e88fc81b54dbf546990d93c2bd7" dependencies = [ - "revm-primitives 1.3.0 (git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858)", + "revm-primitives 2.0.0", "serde", ] [[package]] name = "revm-precompile" -version = "2.2.0" -source = "git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858#30bbcdfe81446c9d1e9b37acc95f208943ddf858" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbf367d7a9515113bf9ab128b64ba80d6e0e238904fb66f8567765ae52b6699" dependencies = [ "aurora-engine-modexp", + "blst", "c-kzg", "k256", "once_cell", - "revm-primitives 1.3.0 (git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858)", + "revm-primitives 2.0.0", "ripemd", "secp256k1", "sha2", @@ -4342,15 +4283,17 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "1.3.0" -source = "git+https://github.com/bluealloy/revm.git?rev=30bbcdfe81446c9d1e9b37acc95f208943ddf858#30bbcdfe81446c9d1e9b37acc95f208943ddf858" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3683a40f1e94e7389c8e81e5f26bb5d30875ed0b48ab07985ec32eb6d6c712" dependencies = [ - "alloy-primitives 0.5.4", - "alloy-rlp", + "alloy-primitives 0.6.2", "auto_impl", "bitflags 2.4.2", "bitvec", + "blst", "c-kzg", + "cfg-if", "derive_more", "enumn", "hashbrown 0.14.3", @@ -4487,12 +4430,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hex" version = "2.1.0" @@ -4743,18 +4680,18 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -4763,9 +4700,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "indexmap", "itoa", @@ -4855,12 +4792,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -5485,14 +5416,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.1", + "toml_edit 0.22.4", ] [[package]] @@ -5533,6 +5464,17 @@ name = "toml_edit" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" dependencies = [ "indexmap", "serde", @@ -5939,18 +5881,6 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "wide" version = "0.7.15" diff --git a/Cargo.toml b/Cargo.toml index 77b89dcbb..583772dc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,25 +34,21 @@ arbiter-bindings = { version = "*", path = "./arbiter-bindings" } arbiter-core = { version = "*", path = "./arbiter-core" } arbiter-macros = { path = "./arbiter-macros" } arbiter-engine = { path = "./arbiter-engine" } +revm = { version = "=4.0.0", features = ["ethersdb", "std", "serde"] } +revm-primitives = "=2.0.0" ethers = { version = "2.0.13" } serde = { version = "1.0.193", features = ["derive"] } -serde_json = { version = "=1.0.108" } -revm = { git = "https://github.com/bluealloy/revm.git", features = [ - "ethersdb", - "std", - "serde", -], rev = "30bbcdfe81446c9d1e9b37acc95f208943ddf858" } -revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "30bbcdfe81446c9d1e9b37acc95f208943ddf858" } +serde_json = { version = "1.0.113" } thiserror = { version = "1.0.55" } syn = { version = "2.0.48", features = ["full"] } proc-macro2 = { version = "1.0.78" } tokio = { version = "1.36.0", features = ["macros", "full"] } crossbeam-channel = { version = "0.5.11" } -futures-util = { version = "=0.3.30" } +futures-util = { version = "0.3.30" } async-trait = { version = "0.1.76" } tracing = "0.1.40" async-stream = "0.3.5" -toml = { version = "=0.8.9" } +toml = "0.8.10" # Dependencies for the release build [dependencies] diff --git a/arbiter-core/Cargo.toml b/arbiter-core/Cargo.toml index 869121df2..bd7e3024b 100644 --- a/arbiter-core/Cargo.toml +++ b/arbiter-core/Cargo.toml @@ -2,7 +2,10 @@ name = "arbiter-core" version = "0.9.1" edition = "2021" -authors = ["Waylon Jepsen ", "Colin Roberts "] +authors = [ + "Waylon Jepsen ", + "Colin Roberts ", +] description = "Allowing smart contract developers to do simulation driven development via an EVM emulator" license = "Apache-2.0" keywords = ["ethereum", "evm", "emulator", "testing", "smart-contracts"] @@ -13,6 +16,7 @@ readme = "../README.md" arbiter-bindings = { path = "../arbiter-bindings" } # Ethereum and EVM +uint = "0.9.5" ethers.workspace = true revm.workspace = true revm-primitives.workspace = true @@ -34,7 +38,7 @@ futures-locks = { version = "=0.7.1" } # Randomness -rand = { version = "=0.8.5" } +rand = { version = "=0.8.5" } rand_distr = { version = "=0.4.3" } statrs = { version = "=0.16.0" } @@ -50,17 +54,17 @@ polars = { version = "0.36.2", features = ["parquet", "csv", "json"] } # Dependencies for the test build and development [dev-dependencies] -anyhow = { version = "=1.0.79" } -test-log = { version = "=0.2.14" } +anyhow = { version = "=1.0.79" } +test-log = { version = "=0.2.14" } tracing-test = "0.2.4" tracing-subscriber = "0.3.18" polars = "0.36.2" cargo_metadata = "0.18.1" chrono = "0.4.33" -futures = { version = "=0.3.30" } +futures = { version = "=0.3.30" } -assert_matches = { version = "=1.5" } +assert_matches = { version = "=1.5" } [[bench]] name = "bench" diff --git a/arbiter-core/benches/bench.rs b/arbiter-core/benches/bench.rs index 75e218e0d..d8665d177 100644 --- a/arbiter-core/benches/bench.rs +++ b/arbiter-core/benches/bench.rs @@ -12,7 +12,7 @@ use arbiter_bindings::bindings::{ }; use arbiter_core::{ environment::{Environment, EnvironmentBuilder}, - middleware::RevmMiddleware, + middleware::ArbiterMiddleware, }; use ethers::{ core::{k256::ecdsa::SigningKey, utils::Anvil}, @@ -177,10 +177,10 @@ async fn anvil_startup() -> Result<( Ok((client, anvil)) } -fn arbiter_startup() -> Result<(Environment, Arc)> { +fn arbiter_startup() -> Result<(Environment, Arc)> { let environment = Environment::builder().build(); - let client = RevmMiddleware::new(&environment, Some("name"))?; + let client = ArbiterMiddleware::new(&environment, Some("name"))?; Ok((environment, client)) } diff --git a/arbiter-core/src/console/mod.rs b/arbiter-core/src/console/mod.rs index abbaa35ef..70dcb5552 100644 --- a/arbiter-core/src/console/mod.rs +++ b/arbiter-core/src/console/mod.rs @@ -1,13 +1,9 @@ //! This module contains the backend for the `console2.log` Solidity function so //! that these logs can be read in Arbiter. -use std::ops::Range; +use revm_primitives::address; -use revm::{ - interpreter::{CallInputs, InterpreterResult}, - Database, EvmContext, Inspector, -}; -use revm_primitives::{address, Address, Bytes}; +use super::*; const CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); @@ -24,9 +20,10 @@ impl Inspector for ConsoleLogs { #[inline] fn call( &mut self, - _context: &mut EvmContext<'_, DB>, + _context: &mut EvmContext, call: &mut CallInputs, - ) -> Option<(InterpreterResult, Range)> { + _return_memory_offset: Range, + ) -> Option { if call.contract == CONSOLE_ADDRESS { self.0.push(call.input.clone()); } diff --git a/arbiter-core/src/coprocessor.rs b/arbiter-core/src/coprocessor.rs index 6a57b4727..2686d60b9 100644 --- a/arbiter-core/src/coprocessor.rs +++ b/arbiter-core/src/coprocessor.rs @@ -1,46 +1,35 @@ -//! The `Coprocessor` is used to process calls and can access read-only from the -//! `Environment`'s database. The `Coprocessor` will stay up to date with the -//! latest state of the `Environment`'s database. +//! The [`Coprocessor`] is used to process calls and can access read-only from +//! the [`Environment`]'s database while staying up to date with the +//! latest state of the [`Environment`]'s database. use std::convert::Infallible; -use revm::EVM; use revm_primitives::{EVMError, ResultAndState}; -use crate::{database::ArbiterDB, environment::Environment}; +use super::*; +use crate::environment::Environment; -// TODO: I have not tested that the coprocessor actually maintains state parity -// with the environment. At the moment, it is only able to be constructed and -// can certainly act as a read-only EVM. - -/// A `Coprocessor` is used to process calls and can access read-only from the -/// `Environment`'s database. This can eventually be used for things like +/// A [`Coprocessor`] is used to process calls and can access read-only from the +/// [`Environment`]'s database. This can eventually be used for things like /// parallelized compute for agents that are not currently sending transactions -/// that need to be processed by the `Environment`, but are instead using the +/// that need to be processed by the [`Environment`], but are instead using the /// current state to make decisions. -pub struct Coprocessor { - evm: EVM, +pub struct Coprocessor<'a> { + evm: Evm<'a, (), ArbiterDB>, } -impl Coprocessor { +impl<'a> Coprocessor<'a> { /// Create a new `Coprocessor` with the given `Environment`. pub fn new(environment: &Environment) -> Self { - let db = ArbiterDB( - environment - .db - .as_ref() - .unwrap_or(&ArbiterDB::new()) - .0 - .clone(), - ); - let mut evm = EVM::new(); - evm.database(db); + let db = environment.db.clone(); + let evm = Evm::builder().with_db(db).build(); Self { evm } } + // TODO: Should probably take in a TxEnv or something. /// Used as an entrypoint to process a call with the `Coprocessor`. - pub fn transact_ref(&self) -> Result> { - self.evm.transact_ref() + pub fn transact(&mut self) -> Result> { + self.evm.transact() } } @@ -54,8 +43,8 @@ mod tests { fn coprocessor() { let environment = Environment::builder().build(); let mut coprocessor = Coprocessor::new(&environment); - coprocessor.evm.env.tx.value = U256::from(100); - let outcome = coprocessor.transact_ref(); + coprocessor.evm.tx_mut().value = U256::from(100); + let outcome = coprocessor.transact(); if let Err(EVMError::Transaction(InvalidTransaction::LackOfFundForMaxFee { fee, balance, diff --git a/arbiter-core/src/data_collection.rs b/arbiter-core/src/data_collection.rs index ed8a63f8e..cabe4924e 100644 --- a/arbiter-core/src/data_collection.rs +++ b/arbiter-core/src/data_collection.rs @@ -18,10 +18,7 @@ //! * `E` - Type that implements the `EthLogDecode`, `Debug`, `Serialize` //! traits, and has a static lifetime. -use std::{ - collections::BTreeMap, fmt::Debug, io::BufWriter, marker::PhantomData, mem::transmute, - sync::Arc, -}; +use std::{io::BufWriter, marker::PhantomData, mem::transmute}; use ethers::{ abi::RawLog, @@ -41,10 +38,7 @@ use serde_json::Value; use tokio::{sync::broadcast::Receiver as BroadcastReceiver, task::JoinHandle}; use super::*; -use crate::{ - environment::Broadcast, - middleware::{cast::revm_logs_to_ethers_logs, errors::RevmMiddlewareError, RevmMiddleware}, -}; +use crate::middleware::{connection::revm_logs_to_ethers_logs, ArbiterMiddleware}; pub(crate) type FilterDecoder = BTreeMap String + Send + Sync>)>; @@ -65,7 +59,6 @@ pub(crate) type FilterDecoder = pub struct EventLogger { decoder: FilterDecoder, receiver: Option>, - // shutdown_sender: Option>, output_file_type: Option, directory: Option, file_name: Option, @@ -134,13 +127,13 @@ impl EventLogger { /// The `EventLogger` instance with the added event. pub fn add, D: EthLogDecode + Debug + Serialize + 'static>( mut self, - event: Event, RevmMiddleware, D>, + event: Event, ArbiterMiddleware, D>, name: S, ) -> Self { let name = name.into(); // Grab the connection from the client and add a new event sender so that we // have a distinct channel to now receive events over - let event_transmuted: EventTransmuted, RevmMiddleware, D> = + let event_transmuted: EventTransmuted, ArbiterMiddleware, D> = unsafe { transmute(event) }; let middleware = event_transmuted.provider.clone(); let decoder = |x: &_| serde_json::to_string(&D::decode_log(x).unwrap()).unwrap(); @@ -162,14 +155,10 @@ impl EventLogger { /// not stored. pub fn add_stream( self, - event: Event, RevmMiddleware, D>, + event: Event, ArbiterMiddleware, D>, ) -> Self { let mut hasher = Sha256::new(); - hasher.update( - serde_json::to_string(&event.filter) - .map_err(RevmMiddlewareError::Json) - .unwrap(), - ); + hasher.update(serde_json::to_string(&event.filter).unwrap()); let hash = hasher.finalize(); let id = hex::encode(hash); self.add(event, id) @@ -258,7 +247,7 @@ impl EventLogger { /// /// This function will return an error if there is a problem creating the /// directories or files, or writing to the files. - pub fn run(self) -> Result, RevmMiddlewareError> { + pub fn run(self) -> Result, ArbiterCoreError> { let mut receiver = self.receiver.unwrap(); let dir = self.directory.unwrap_or("./data".into()); let file_name = self.file_name.unwrap_or("output".into()); diff --git a/arbiter-core/src/environment/fork.rs b/arbiter-core/src/database/fork.rs similarity index 83% rename from arbiter-core/src/environment/fork.rs rename to arbiter-core/src/database/fork.rs index f4aa360ed..b68813109 100644 --- a/arbiter-core/src/environment/fork.rs +++ b/arbiter-core/src/database/fork.rs @@ -4,9 +4,7 @@ //! that the [`Environment`] can be initialized with a forked database and the //! end-user still has access to the relevant metadata. -use std::{collections::HashMap, env, fs}; - -use ethers::types::Address; +use std::{env, fs}; use super::*; @@ -15,7 +13,7 @@ use super::*; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ContractMetadata { /// The address of the contract. - pub address: Address, + pub address: eAddress, /// The path to the contract artifacts. pub artifacts_path: String, @@ -27,8 +25,8 @@ pub struct ContractMetadata { /// A [`Fork`] is used to store the data that will be loaded into an /// [`Environment`] and be used in `arbiter-core`. It is a wrapper around a /// [`CacheDB`] and a [`HashMap`] of [`ContractMetadata`] so that the -/// [`Environment`] can be initialized with the data and the end-user still has -/// access to the relevant metadata. +/// [`environment::Environment`] can be initialized with the data and the +/// end-user still has access to the relevant metadata. #[derive(Clone, Debug)] pub struct Fork { /// The [`CacheDB`] that will be loaded into the [`Environment`]. @@ -38,12 +36,12 @@ pub struct Fork { /// end-user. pub contracts_meta: HashMap, /// The [`HashMap`] of [`Address`] that will be used by the end-user. - pub eoa: HashMap, + pub eoa: HashMap, } impl Fork { /// Creates a new [`Fork`] from serialized [`DiskData`] stored on disk. - pub fn from_disk(path: &str) -> Result { + pub fn from_disk(path: &str) -> Result { // Read the file let mut cwd = env::current_dir().unwrap(); cwd.push(path); @@ -66,8 +64,8 @@ impl Fork { // Insert storage data into the DB for (key_str, value_str) in storage_map { - let key = revm::primitives::U256::from_str_radix(&key_str, 10).unwrap(); - let value = revm::primitives::U256::from_str_radix(&value_str, 10).unwrap(); + let key = U256::from_str_radix(&key_str, 10).unwrap(); + let value = U256::from_str_radix(&value_str, 10).unwrap(); db.insert_account_storage(address, key, value).unwrap(); } @@ -98,8 +96,8 @@ pub struct DiskData { pub meta: HashMap, /// This is the raw data that will be loaded into the [`Fork`]. - pub raw: HashMap, + pub raw: HashMap, /// This is the eoa data that will be loaded into the [`Fork`]. - pub externally_owned_accounts: HashMap, + pub externally_owned_accounts: HashMap, } diff --git a/arbiter-core/src/inspector.rs b/arbiter-core/src/database/inspector.rs similarity index 70% rename from arbiter-core/src/inspector.rs rename to arbiter-core/src/database/inspector.rs index 6d5b90b8c..51f80e17f 100644 --- a/arbiter-core/src/inspector.rs +++ b/arbiter-core/src/database/inspector.rs @@ -1,20 +1,17 @@ -//! This module contains an extensible [`revm::Inspector`] called +//! This module contains an extensible [`Inspector`] called //! [`ArbiterInspector`]. It is currently configurable in order to allow //! for users to set configuration to see logs generated in Solidity contracts //! and or enforce gas payment. -use std::ops::Range; - use revm::{ inspectors::GasInspector, - interpreter::{CallInputs, Interpreter, InterpreterResult}, - Database, EvmContext, Inspector, + interpreter::{CreateInputs, CreateOutcome, Interpreter}, }; -use revm_primitives::Address; +use super::*; use crate::console::ConsoleLogs; -/// An configurable [`revm::Inspector`] that collects information about the +/// An configurable [`Inspector`] that collects information about the /// execution of the [`Interpreter`]. Depending on whether which or both /// features are enabled, it collects information about the gas used by each /// opcode and the `console2.log`s emitted during execution. It ensures gas @@ -47,14 +44,14 @@ impl ArbiterInspector { impl Inspector for ArbiterInspector { #[inline] - fn initialize_interp(&mut self, interp: &mut Interpreter, context: &mut EvmContext<'_, DB>) { + fn initialize_interp(&mut self, interp: &mut Interpreter, context: &mut EvmContext) { if let Some(gas) = &mut self.gas { gas.initialize_interp(interp, context); } } #[inline] - fn step_end(&mut self, interp: &mut Interpreter, context: &mut EvmContext<'_, DB>) { + fn step_end(&mut self, interp: &mut Interpreter, context: &mut EvmContext) { if let Some(gas) = &mut self.gas { gas.step_end(interp, context); } @@ -63,11 +60,12 @@ impl Inspector for ArbiterInspector { #[inline] fn call( &mut self, - context: &mut EvmContext<'_, DB>, + context: &mut EvmContext, inputs: &mut CallInputs, - ) -> Option<(InterpreterResult, Range)> { + return_memory_offset: Range, + ) -> Option { if let Some(console_log) = &mut self.console_log { - console_log.call(context, inputs) + console_log.call(context, inputs, return_memory_offset) } else { None } @@ -76,23 +74,24 @@ impl Inspector for ArbiterInspector { #[inline] fn call_end( &mut self, - context: &mut EvmContext<'_, DB>, - result: InterpreterResult, - ) -> InterpreterResult { + context: &mut EvmContext, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { if let Some(gas) = &mut self.gas { - gas.call_end(context, result) + gas.call_end(context, inputs, outcome) } else { - result + outcome } } #[inline] fn create_end( &mut self, - _context: &mut EvmContext<'_, DB>, - result: InterpreterResult, - address: Option
, - ) -> (InterpreterResult, Option
) { - (result, address) + _context: &mut EvmContext, + _inputs: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + outcome } } diff --git a/arbiter-core/src/database.rs b/arbiter-core/src/database/mod.rs similarity index 92% rename from arbiter-core/src/database.rs rename to arbiter-core/src/database/mod.rs index 812d970f1..10e9ab1ab 100644 --- a/arbiter-core/src/database.rs +++ b/arbiter-core/src/database/mod.rs @@ -1,30 +1,31 @@ -//! The `ArbiterDB` is a wrapper around a `CacheDB` that is used to provide +//! The [`ArbiterDB`] is a wrapper around a `CacheDB` that is used to provide //! access to the `Environment`'s database to multiple `Coprocessors`. //! It is also used to be able to write out the `Environment` database to a //! file. +//! +//! Further, it gives the ability to be generated from a [`fork::Fork`] so that +//! you can preload an [`environment::Environment`] with a specific state. use std::{ - collections::BTreeMap, - convert::Infallible, - fmt::Debug, fs, io::{self, Read, Write}, - sync::{Arc, RwLock}, }; use revm::{ - db::{CacheDB, EmptyDB}, - primitives::{AccountInfo, B256, U256}, - Database, DatabaseCommit, + primitives::{db::DatabaseRef, keccak256, Bytecode, B256}, + DatabaseCommit, }; -use revm_primitives::{db::DatabaseRef, keccak256, Address, Bytecode, Bytes}; -use serde::{Deserialize, Serialize}; use serde_json; -/// A `ArbiterDB` is a wrapper around a `CacheDB` that is used to provide -/// access to the `Environment`'s database to multiple `Coprocessors`. +use super::*; +pub mod fork; +pub mod inspector; + +/// A [`ArbiterDB`] is a wrapper around a [`CacheDB`] that is used to provide +/// access to the [`environment::Environment`]'s database to multiple +/// [`coprocessor::Coprocessor`]s. #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ArbiterDB(pub(crate) Arc>>); +pub struct ArbiterDB(pub Arc>>); impl ArbiterDB { /// Create a new `ArbiterDB`. diff --git a/arbiter-core/src/environment/cheatcodes.rs b/arbiter-core/src/environment/cheatcodes.rs deleted file mode 100644 index 7967c722e..000000000 --- a/arbiter-core/src/environment/cheatcodes.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Cheatcodes are a direct way to access the underlying [`EVM`] environment -// and database. ! Use them via the `apply_cheatcode` method on a `client`. - -use revm_primitives::{AccountInfo, HashMap, U256}; - -/// Cheatcodes are a direct way to access the underlying [`EVM`] environment and -/// database. -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum Cheatcodes { - /// A `Deal` is used to increase the balance of an account in the [`EVM`]. - Deal { - /// The address of the account to increase the balance of. - address: ethers::types::Address, - - /// The amount to increase the balance of the account by. - amount: ethers::types::U256, - }, - /// Fetches the value of a storage slot of an account. - Load { - /// The address of the account to fetch the storage slot from. - account: ethers::types::Address, - /// The storage slot to fetch. - key: ethers::types::H256, - /// The block to fetch the storage slot from. - /// todo: implement storage slots at blocks. - block: Option, - }, - /// Overwrites a storage slot of an account. - /// TODO: for more complicated data types, like structs, there's more work - /// to do. - Store { - /// The address of the account to overwrite the storage slot of. - account: ethers::types::Address, - /// The storage slot to overwrite. - key: ethers::types::H256, - /// The value to overwrite the storage slot with. - value: ethers::types::H256, - }, - /// Fetches the `DbAccount` account at the given address. - Access { - /// The address of the account to fetch. - address: ethers::types::Address, - }, -} - -/// Wrapper around [`AccountState`] that can be serialized and deserialized. -#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub enum AccountStateSerializable { - /// Before Spurious Dragon hardfork there was a difference between empty and - /// not existing. And we are flagging it here. - NotExisting, - /// EVM touched this account. For newer hardfork this means it can be - /// cleared/removed from state. - Touched, - /// EVM cleared storage of this account, mostly by selfdestruct, we don't - /// ask database for storage slots and assume they are U256::ZERO - StorageCleared, - /// EVM didn't interacted with this account - #[default] - None, -} - -/// Return values of applying cheatcodes. -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum CheatcodesReturn { - /// A `Load` returns the value of a storage slot of an account. - Load { - /// The value of the storage slot. - value: revm::primitives::U256, - }, - /// A `Store` returns nothing. - Store, - /// A `Deal` returns nothing. - Deal, - /// Gets the DbAccount associated with an address. - Access { - /// Basic account information like nonce, balance, code hash, bytcode. - info: AccountInfo, - /// todo: revm must be updated with serde deserialize, then `DbAccount` - /// can be used. - account_state: AccountStateSerializable, - /// Storage slots of the account. - storage: HashMap, - }, -} diff --git a/arbiter-core/src/environment/errors.rs b/arbiter-core/src/environment/errors.rs deleted file mode 100644 index f21195fb0..000000000 --- a/arbiter-core/src/environment/errors.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Errors that can occur when managing or interfacing with Arbiter's sandboxed -//! Ethereum environment. - -use super::*; - -/// Errors that can occur when managing or interfacing with the Ethereum -/// environment. -/// -/// ## What are we trying to catch? -/// The errors here are at a fairly low level and should be quite rare (if -/// possible). Errors that come from smart contracts (e.g., reverts or halts) -/// will not be caught here and will instead carried out into the -/// [`RevmMiddleware`]. Please bring up if you catch errors here by sending a -/// message in the [Telegram group](https://t.me/arbiter_rs) or on -/// [GitHub](https://github.com/primitivefinance/arbiter/). -#[derive(Error, Debug, Clone)] -pub enum EnvironmentError { - /// [`EnvironmentError::Execution`] is thrown when the [`EVM`] itself - /// throws an error in execution. To be clear, this is not a contract - /// revert or halt, this is likely an error in `revm`. Please report - /// this type of error. - #[error("execution error! the source error is: {0:?}")] - Execution(#[from] EVMError), - - /// [`EnvironmentError::Transaction`] is thrown when a transaction fails - /// to be processed by the [`EVM`]. This could be due to a insufficient - /// funds to pay for gas, an invalid nonce, or other reasons. This error - /// can be quite common and should be handled gracefully. - #[error("transaction error! the source error is: {0:?}")] - Transaction(InvalidTransaction), - - /// [`EnvironmentError::Account`] is thrown when there is an issue handling - /// accounts in the [`EVM`]. This could be due to an account already - /// existing or other reasons. - #[error("account error! due to: {0:?}")] - Account(String), - - /// [`EnvironmentError::Stop`] is thrown when the [`Environment`] - /// fails to stop. This error could occur due to an invalid state transition - /// or other unexpected conditions. If this error is thrown, it indicates - /// a serious issue that needs to be investigated. Please report this error! - #[error("error stopping! due to: {0:?}")] - Stop(String), - - /// [`EnvironmentError::Communication`] is thrown when a channel for - /// receiving or broadcasting fails in some way. This error could happen - /// due to a channel being closed accidentally. If this is thrown, a - /// restart of the simulation and an investigation into what caused a - /// dropped channel is necessary. - #[error("error communicating! due to: {0}")] - Communication(String), - - /// [`EnvironmentError::Broadcast`] is thrown when the - /// [`EventBroadcaster`] fails to broadcast events. This should be - /// rare (if not impossible). If this is thrown, please report this error! - #[error("error broadcasting! the source error is: {0}")] - Broadcast(#[from] crossbeam_channel::SendError), - - /// [`EnvironmentError::Conversion`] is thrown when a type fails to - /// convert into another (typically a type used in `revm` versus a type used - /// in [`ethers-rs`](https://github.com/gakonst/ethers-rs)). - /// This error should be rare (if not impossible). - /// Furthermore, after a switch to [`alloy`](https://github.com/alloy-rs) - /// this will be (hopefully) unnecessary! - #[error("conversion error! the source error is: {0}")] - Conversion(String), - - /// [`EnvironmentError::ShutDownReceiverError`] is thrown when a malformed - /// shutdown receiver is sent to the event broadcaster. This error could - /// occur due to an invalid shutdown receiver. - #[error("error in the environment! malformed shutdown receiver sent to event broadcaster")] - ShutDownReceiverError, -} diff --git a/arbiter-core/src/environment/instruction.rs b/arbiter-core/src/environment/instruction.rs index ed268ce2e..e9e159e8f 100644 --- a/arbiter-core/src/environment/instruction.rs +++ b/arbiter-core/src/environment/instruction.rs @@ -1,3 +1,7 @@ +//! This module contains the `Instruction` and `Outcome` enums that are used to +//! communicate instructions and their outcomes between the +//! [`middleware::ArbiterMiddleware`] and the [`Environment`]. + use super::*; /// [`Instruction`]s that can be sent to the [`Environment`] via the @@ -18,10 +22,10 @@ use super::*; #[derive(Debug, Clone)] pub(crate) enum Instruction { /// An `AddAccount` is used to add a default/unfunded account to the - /// [`EVM`]. + /// [`Environment`]. AddAccount { /// The address of the account to add to the [`EVM`]. - address: ethers::types::Address, + address: eAddress, /// The sender used to to send the outcome of the account addition back /// to. @@ -29,13 +33,13 @@ pub(crate) enum Instruction { }, /// A `BlockUpdate` is used to update the block number and timestamp of the - /// [`EVM`]. + /// [`Environment`]. BlockUpdate { /// The block number to update the [`EVM`] to. - block_number: U256, + block_number: eU256, /// The block timestamp to update the [`EVM`] to. - block_timestamp: U256, + block_timestamp: eU256, /// The sender used to to send the outcome of the block update back to. outcome_sender: OutcomeSender, @@ -73,7 +77,7 @@ pub(crate) enum Instruction { /// A `SetGasPrice` is used to set the gas price of the [`EVM`]. SetGasPrice { /// The gas price to set the [`EVM`] to. - gas_price: ethers::types::U256, + gas_price: eU256, /// The sender used to to send the outcome of the gas price setting back /// to. @@ -152,11 +156,11 @@ pub(crate) enum EnvironmentData { GasPrice, /// The query is for the balance of an account given by the inner `Address`. - Balance(ethers::types::Address), + Balance(eAddress), // TODO: Rename this to `Nonce`? /// The query is for the nonce of an account given by the inner `Address`. - TransactionCount(ethers::types::Address), + TransactionCount(eAddress), } /// [`ReceiptData`] is a structure that holds the block number, transaction @@ -165,11 +169,92 @@ pub(crate) enum EnvironmentData { pub struct ReceiptData { /// `block_number` is the number of the block in which the transaction was /// included. - pub(crate) block_number: U64, + pub block_number: U64, /// `transaction_index` is the index position of the transaction in the /// block. - pub(crate) transaction_index: U64, - /// [`cumulative_gas_per_block`] is the total amount of gas used in the + pub transaction_index: U64, + /// `cumulative_gas_per_block` is the total amount of gas used in the /// block up until and including the transaction. - pub(crate) cumulative_gas_per_block: U256, + pub cumulative_gas_per_block: eU256, +} + +/// Cheatcodes are a direct way to access the underlying [`EVM`] environment and +/// database. +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum Cheatcodes { + /// A `Deal` is used to increase the balance of an account in the [`EVM`]. + Deal { + /// The address of the account to increase the balance of. + address: eAddress, + + /// The amount to increase the balance of the account by. + amount: eU256, + }, + /// Fetches the value of a storage slot of an account. + Load { + /// The address of the account to fetch the storage slot from. + account: eAddress, + /// The storage slot to fetch. + key: H256, + /// The block to fetch the storage slot from. + /// todo: implement storage slots at blocks. + block: Option, + }, + /// Overwrites a storage slot of an account. + /// TODO: for more complicated data types, like structs, there's more work + /// to do. + Store { + /// The address of the account to overwrite the storage slot of. + account: ethers::types::Address, + /// The storage slot to overwrite. + key: ethers::types::H256, + /// The value to overwrite the storage slot with. + value: ethers::types::H256, + }, + /// Fetches the `DbAccount` account at the given address. + Access { + /// The address of the account to fetch. + address: ethers::types::Address, + }, +} + +/// Wrapper around [`AccountState`] that can be serialized and deserialized. +#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum AccountStateSerializable { + /// Before Spurious Dragon hardfork there was a difference between empty and + /// not existing. And we are flagging it here. + NotExisting, + /// EVM touched this account. For newer hardfork this means it can be + /// cleared/removed from state. + Touched, + /// EVM cleared storage of this account, mostly by selfdestruct, we don't + /// ask database for storage slots and assume they are U256::ZERO + StorageCleared, + /// EVM didn't interacted with this account + #[default] + None, +} + +/// Return values of applying cheatcodes. +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum CheatcodesReturn { + /// A `Load` returns the value of a storage slot of an account. + Load { + /// The value of the storage slot. + value: U256, + }, + /// A `Store` returns nothing. + Store, + /// A `Deal` returns nothing. + Deal, + /// Gets the DbAccount associated with an address. + Access { + /// Basic account information like nonce, balance, code hash, bytcode. + info: AccountInfo, + /// todo: revm must be updated with serde deserialize, then `DbAccount` + /// can be used. + account_state: AccountStateSerializable, + /// Storage slots of the account. + storage: HashMap, + }, } diff --git a/arbiter-core/src/environment/mod.rs b/arbiter-core/src/environment/mod.rs index 9ebb705d1..943a9ff24 100644 --- a/arbiter-core/src/environment/mod.rs +++ b/arbiter-core/src/environment/mod.rs @@ -1,74 +1,43 @@ -//! The `environment` module provides abstractions and functionality for +//! The [`environment`] module provides abstractions and functionality for //! handling the Ethereum execution environment. This includes managing its //! state, interfacing with the EVM, and broadcasting events to subscribers. +//! Other features include the ability to control block rate and gas settings +//! and execute other database modifications from external agents. //! //! The key integration for the environment is the Rust EVM [`revm`](https://github.com/bluealloy/revm). //! This is an implementation of the EVM in Rust that we utilize for processing //! raw smart contract bytecode. //! //! Core structures: -//! - `Environment`: Represents the Ethereum execution environment, allowing for -//! its management (e.g., starting, stopping) and interfacing with agents. -//! - `EnvironmentParameters`: Parameters necessary for creating or modifying -//! an `Environment`. -//! - `BlockSettings`: Enum indicating how block numbers and timestamps are -//! moved forward. -//! - `GasSettings`: Enum indicating the type of gas settings that will be -//! used to make clients pay gas. -//! - `Instruction`: Enum indicating the type of instruction that is being sent +//! - [`Environment`]: Represents the Ethereum execution environment, allowing +//! for its management (e.g., starting, stopping) and interfacing with agents. +//! - [`EnvironmentParameters`]: Parameters necessary for creating or modifying +//! an [`Environment`]. +//! - [`Instruction`]: Enum indicating the type of instruction that is being +//! sent //! to the EVM. -//! - `Outcome`: Enum indicating the type of outcome that is being sent back -//! from the EVM. -//! - `EnvironmentError`: Enum indicating the type of error that can be thrown -//! by the EVM. -//! - `State`: Enum indicating the current state of the environment. -//! - `Socket`: Provides channels for communication between the EVM and the -//! outside world. -//! - `EventBroadcaster`: Responsible for broadcasting Ethereum logs to -//! subscribers. - -use std::{ - convert::Infallible, - fmt::Debug, - sync::{Arc, RwLock}, - thread::{self, JoinHandle}, -}; + +use std::thread::{self, JoinHandle}; use crossbeam_channel::{bounded, unbounded, Receiver, Sender}; -use ethers::{abi::AbiDecode, core::types::U64}; +use ethers::abi::AbiDecode; use revm::{ - db::{CacheDB, EmptyDB}, - primitives::{ - AccountInfo, EVMError, ExecutionResult, HashMap, InvalidTransaction, Log, TxEnv, U256, - }, - EVM, + db::AccountState, + inspector_handle_register, + primitives::{Env, HashMap, B256}, }; -use serde::{Deserialize, Serialize}; -use thiserror::Error; -use tokio::sync::broadcast::{channel, Sender as BroadcastSender}; +use tokio::sync::broadcast::channel; -use self::inspector::ArbiterInspector; use super::*; -use crate::database::ArbiterDB; #[cfg_attr(doc, doc(hidden))] #[cfg_attr(doc, allow(unused_imports))] #[cfg(doc)] -use crate::middleware::RevmMiddleware; - -pub mod cheatcodes; -use cheatcodes::*; +use crate::middleware::ArbiterMiddleware; +use crate::{console::abi::HardhatConsoleCalls, database::inspector::ArbiterInspector}; -pub(crate) mod instruction; +pub mod instruction; use instruction::*; -pub mod errors; -use errors::*; - -pub mod fork; - -#[cfg(test)] -pub(crate) mod tests; - /// Alias for the sender of the channel for transmitting transactions. pub(crate) type InstructionSender = Sender; @@ -77,48 +46,33 @@ pub(crate) type InstructionReceiver = Receiver; /// Alias for the sender of the channel for transmitting [`RevmResult`] emitted /// from transactions. -pub(crate) type OutcomeSender = Sender>; +pub(crate) type OutcomeSender = Sender>; /// Alias for the receiver of the channel for transmitting [`RevmResult`] /// emitted from transactions. -pub(crate) type OutcomeReceiver = Receiver>; +pub(crate) type OutcomeReceiver = Receiver>; /// Represents a sandboxed EVM environment. /// -/// ## Communication -/// The dominant feature is the -/// [`EVM`](https://github.com/bluealloy/revm/blob/main/crates/revm/src/evm.rs) -/// and its connections to the "outside world". -/// The Ethereum Virtual Machine -/// ([`EVM`](https://github.com/bluealloy/revm/blob/main/crates/revm/src/evm.rs)) -/// which is a stack machine that processes raw smart contract bytecode and -/// updates a local database of the worldstate of an Ethereum simulation. -/// Note, the worldstate of the simulation Ethereum environment should not be -/// confused with the [`State`] of the environment here! The [`Environment`] -/// will route transactions sent over channels to the stack machine -/// [`EVM`](https://github.com/bluealloy/revm/blob/main/crates/revm/src/evm.rs) -/// to process smart contract interactions. -/// It provides channels for sending transactions to the EVM and for -/// receiving results or broadcasting events to any subscribers via the -/// `Socket` field exposed only as `pub(crate)`. -/// -/// -/// ## Controlling Block Rate -/// The blocks for the [`Environment`] are chosen using a Poisson distribution -/// via the [`SeededPoisson`] field. The idea is that we can choose a rate -/// parameter, typically denoted by the Greek letter lambda, and set this to be -/// the expected number of transactions per block while allowing blocks to be -/// built with random size. This is useful in stepping forward the -/// [`EVM`](https://github.com/bluealloy/revm/blob/main/crates/revm/src/evm.rs) -/// and being able to move time forward for contracts that depend explicitly on -/// time. +/// ## Features +/// * [`revm::Evm`] and its connections to the "outside world" (agents) via the +/// [`Socket`] provide the [`Environment`] a means to route and execute +/// transactions. +/// * [`ArbiterDB`] is the database structure used that allows for read-only +/// sharing of execution and write-only via the main thread. This can also be a +/// database read in from disk storage via [`database::fork::Fork`]. +/// * [`ArbiterInspector`] is an that allows for the EVM to be able to display +/// logs and properly handle gas payments. +/// * [`EnvironmentParameters`] are used to set the gas limit, contract size +/// limit, and label for the [`Environment`]. +#[derive(Debug)] pub struct Environment { /// The label used to define the [`Environment`]. pub parameters: EnvironmentParameters, /// The [`EVM`] that is used as an execution environment and database for /// calls and transactions. - pub(crate) db: Option, + pub(crate) db: ArbiterDB, inspector: Option, @@ -130,23 +84,10 @@ pub struct Environment { /// [`JoinHandle`] for the thread in which the [`EVM`] is running. /// Used for assuring that the environment is stopped properly or for /// performing any blocking action the end user needs. - pub(crate) handle: Option>>, + pub(crate) handle: Option>>, } -/// Allow the end user to be able to access a debug printout for the -/// [`Environment`]. Note that the [`EVM`] does not implement debug display, -/// hence the implementation by hand here. -impl Debug for Environment { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Environment") - .field("parameters", &self.parameters) - .field("socket", &self.socket) - .field("handle", &self.handle) - .finish() - } -} - -/// Parameters necessary for creating or modifying an [`Environment`]. +/// Parameters to create [`Environment`]s with different settings. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct EnvironmentParameters { /// The label used to define the [`Environment`]. @@ -173,7 +114,7 @@ pub struct EnvironmentParameters { /// size limit, and a database for the [`Environment`]. pub struct EnvironmentBuilder { parameters: EnvironmentParameters, - db: Option, + db: ArbiterDB, } impl EnvironmentBuilder { @@ -204,7 +145,7 @@ impl EnvironmentBuilder { /// Sets the database for the [`Environment`]. This can come from a /// [`fork::Fork`]. pub fn with_db(mut self, db: impl Into>) -> Self { - self.db = Some(ArbiterDB(Arc::new(RwLock::new(db.into())))); + self.db = ArbiterDB(Arc::new(RwLock::new(db.into()))); self } @@ -229,11 +170,11 @@ impl Environment { pub fn builder() -> EnvironmentBuilder { EnvironmentBuilder { parameters: EnvironmentParameters::default(), - db: None, + db: ArbiterDB(Arc::new(RwLock::new(CacheDB::new(EmptyDB::new())))), } } - fn create(parameters: EnvironmentParameters, db: Option) -> Self { + fn create(parameters: EnvironmentParameters, db: ArbiterDB) -> Self { let (instruction_sender, instruction_receiver) = unbounded(); let (event_broadcaster, _) = channel(512); let socket = Socket { @@ -260,26 +201,21 @@ impl Environment { } } - /// The [`EVM`] will be - /// offloaded onto a separate thread for processing. - /// Calls, transactions, and events will enter/exit through the `Socket`. + /// This starts the [`Environment`] thread to process any [`Instruction`]s + /// coming through the [`Socket`]. fn run(mut self) -> Self { - // Initialize the EVM used with a DB. - let mut evm = EVM::new(); - if self.db.is_some() { - evm.database(self.db.as_ref().unwrap().clone()); - } else { - evm.database(ArbiterDB(Arc::new(RwLock::new(CacheDB::new( - EmptyDB::new(), - ))))); - }; - // Bring in parameters for the `Environment`. let label = self.parameters.label.clone(); - evm.env.cfg.limit_contract_code_size = - Some(self.parameters.contract_size_limit.unwrap_or(0x100_000)); - evm.env.block.gas_limit = self.parameters.gas_limit.unwrap_or(U256::MAX); - let mut inspector = self.inspector.take().unwrap(); + + // Bring in the EVM db by cloning the interior Arc (lightweight). + let db = self.db.clone(); + + // Bring in the EVM ENV + let mut env = Env::default(); + env.cfg.limit_contract_code_size = self.parameters.contract_size_limit; + env.block.gas_limit = self.parameters.gas_limit.unwrap_or(U256::MAX); + // Bring in the inspector + let inspector = self.inspector.take().unwrap(); // Pull communication clones to move into a new thread. let instruction_receiver = self.socket.instruction_receiver.clone(); @@ -287,9 +223,17 @@ impl Environment { // Move the EVM and its socket to a new thread and retrieve this handle let handle = thread::spawn(move || { + // Create a new EVM builder + let mut evm = Evm::builder() + .with_db(db) + .with_env(Box::new(env)) + .with_external_context(inspector) + .append_handler_register(inspector_handle_register) + .build(); + // Initialize counters that are returned on some receipts. let mut transaction_index = U64::from(0_u64); - let mut cumulative_gas_per_block = U256::from(0); + let mut cumulative_gas_per_block = eU256::from(0); // Loop over the instructions sent through the socket. while let Ok(instruction) = instruction_receiver.recv() { @@ -303,14 +247,13 @@ impl Environment { address, outcome_sender, } => { - let db = evm.db.as_mut().unwrap(); - let recast_address = - revm::primitives::Address::from(address.as_fixed_bytes()); + let recast_address = Address::from(address.as_fixed_bytes()); let account = revm::db::DbAccount { info: AccountInfo::default(), - account_state: revm::db::AccountState::None, + account_state: AccountState::None, storage: HashMap::new(), }; + let db = &mut evm.context.evm.db; match db .0 .write() @@ -318,17 +261,9 @@ impl Environment { .accounts .insert(recast_address, account) { - None => { - outcome_sender - .send(Ok(Outcome::AddAccountCompleted)) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; - } + None => outcome_sender.send(Ok(Outcome::AddAccountCompleted))?, Some(_) => { - outcome_sender - .send(Err(EnvironmentError::Account( - "Account already exists!".to_string(), - ))) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; + outcome_sender.send(Err(ArbiterCoreError::AccountCreationError))?; } } } @@ -339,21 +274,19 @@ impl Environment { } => { // Return the old block data in a `ReceiptData` let receipt_data = ReceiptData { - block_number: convert_uint_to_u64(evm.env.block.number).unwrap(), + block_number: convert_uint_to_u64(evm.block().number).unwrap(), transaction_index, cumulative_gas_per_block, }; - outcome_sender - .send(Ok(Outcome::BlockUpdateCompleted(receipt_data))) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; + outcome_sender.send(Ok(Outcome::BlockUpdateCompleted(receipt_data)))?; // Update the block number and timestamp - evm.env.block.number = block_number; - evm.env.block.timestamp = block_timestamp; + evm.block_mut().number = U256::from_limbs(block_number.0); + evm.block_mut().timestamp = U256::from_limbs(block_timestamp.0); // Reset the counters. transaction_index = U64::from(0); - cumulative_gas_per_block = U256::from(0); + cumulative_gas_per_block = eU256::from(0); } Instruction::Cheatcode { cheatcode, @@ -364,45 +297,27 @@ impl Environment { key, block: _, } => { - // Get the underlying database. - let db = evm.db.as_mut().unwrap(); + let db = &mut evm.context.evm.db; - // Cast the ethers-rs cheatcode arguments into revm types. - let recast_address = - revm::primitives::Address::from(account.as_fixed_bytes()); - let recast_key = revm::primitives::B256::from(key.as_fixed_bytes()); + let recast_address = Address::from(account.as_fixed_bytes()); + let recast_key = B256::from(key.as_fixed_bytes()).into(); // Get the account storage value at the key in the db. match db.0.write().unwrap().accounts.get_mut(&recast_address) { Some(account) => { // Returns zero if the account is missing. - let value: revm::primitives::U256 = match account - .storage - .get::( - &recast_key.into(), - ) { + let value: U256 = match account.storage.get::(&recast_key) + { Some(value) => *value, - None => revm::primitives::U256::ZERO, + None => U256::ZERO, }; - - // Sends the revm::primitives::U256 storage value back to the - // sender via CheatcodeReturn(revm::primitives::U256). - outcome_sender - .send(Ok(Outcome::CheatcodeReturn( - CheatcodesReturn::Load { value }, - ))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + outcome_sender.send(Ok(Outcome::CheatcodeReturn( + CheatcodesReturn::Load { value }, + )))?; } None => { outcome_sender - .send(Err(EnvironmentError::Account( - "Account is missing!".to_string(), - ))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + .send(Err(ArbiterCoreError::AccountDoesNotExistError))?; } }; } @@ -412,83 +327,59 @@ impl Environment { value, } => { // Get the underlying database - let db = evm.db.as_mut().unwrap(); + let db = &mut evm.context.evm.db; - // Cast the ethers-rs types passed in the cheatcode arguments into revm - // primitive types - let recast_address = - revm::primitives::Address::from(account.as_fixed_bytes()); - let recast_key = revm::primitives::B256::from(key.as_fixed_bytes()); - let recast_value = revm::primitives::B256::from(value.as_fixed_bytes()); + let recast_address = Address::from(account.as_fixed_bytes()); + let recast_key = B256::from(key.as_fixed_bytes()); + let recast_value = B256::from(value.as_fixed_bytes()); // Mutate the db by inserting the new key-value pair into the account's - // storage and send the successful - // CheatcodeCompleted outcome. + // storage and send the successful CheatcodeCompleted outcome. match db.0.write().unwrap().accounts.get_mut(&recast_address) { Some(account) => { account .storage .insert(recast_key.into(), recast_value.into()); - outcome_sender - .send(Ok(Outcome::CheatcodeReturn(CheatcodesReturn::Store))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + outcome_sender.send(Ok(Outcome::CheatcodeReturn( + CheatcodesReturn::Store, + )))?; } None => { outcome_sender - .send(Err(EnvironmentError::Account( - "Account is missing!".to_string(), - ))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + .send(Err(ArbiterCoreError::AccountDoesNotExistError))?; } }; } Cheatcodes::Deal { address, amount } => { - let db = evm.db.as_mut().unwrap(); - let recast_address = - revm::primitives::Address::from(address.as_fixed_bytes()); + let db = &mut evm.context.evm.db; + let recast_address = Address::from(address.as_fixed_bytes()); match db.0.write().unwrap().accounts.get_mut(&recast_address) { Some(account) => { account.info.balance += U256::from_limbs(amount.0); - outcome_sender - .send(Ok(Outcome::CheatcodeReturn(CheatcodesReturn::Deal))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + outcome_sender.send(Ok(Outcome::CheatcodeReturn( + CheatcodesReturn::Deal, + )))?; } None => { outcome_sender - .send(Err(EnvironmentError::Account( - "Account is missing!".to_string(), - ))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + .send(Err(ArbiterCoreError::AccountDoesNotExistError))?; } }; } Cheatcodes::Access { address } => { - let db = evm.db.as_mut().unwrap(); - let recast_address = - revm::primitives::Address::from(address.as_fixed_bytes()); + let db = &mut evm.context.evm.db; + let recast_address = Address::from(address.as_fixed_bytes()); match db.0.write().unwrap().accounts.get(&recast_address) { Some(account) => { let account_state = match account.account_state { - revm::db::AccountState::None => { - AccountStateSerializable::None - } - revm::db::AccountState::Touched => { - AccountStateSerializable::Touched - } - revm::db::AccountState::StorageCleared => { + AccountState::None => AccountStateSerializable::None, + AccountState::Touched => AccountStateSerializable::Touched, + AccountState::StorageCleared => { AccountStateSerializable::StorageCleared } - revm::db::AccountState::NotExisting => { + AccountState::NotExisting => { AccountStateSerializable::NotExisting } }; @@ -499,20 +390,11 @@ impl Environment { storage: account.storage.clone(), }; - outcome_sender - .send(Ok(Outcome::CheatcodeReturn(account))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + outcome_sender.send(Ok(Outcome::CheatcodeReturn(account)))?; } None => { outcome_sender - .send(Err(EnvironmentError::Account( - "Account is missing!".to_string(), - ))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; + .send(Err(ArbiterCoreError::AccountDoesNotExistError))?; } } } @@ -524,33 +406,28 @@ impl Environment { outcome_sender, } => { // Set the tx_env and prepare to process it - evm.env.tx = tx_env; + *evm.tx_mut() = tx_env; - let result = evm.inspect_ref(&mut inspector)?.result; + // TODO: Is `transact()` the function we want? + let result = evm.transact()?.result; - if let Some(inspector) = &mut inspector.console_log { - inspector.0.drain(..).for_each(|log| { + if let Some(console_log) = &mut evm.context.external.console_log { + console_log.0.drain(..).for_each(|log| { trace!( "Console logs: {:?}", - console::abi::HardhatConsoleCalls::decode(log) - .unwrap() - .to_string() + HardhatConsoleCalls::decode(log).unwrap().to_string() ) }); }; - outcome_sender - .send(Ok(Outcome::CallCompleted(result))) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; + outcome_sender.send(Ok(Outcome::CallCompleted(result)))?; } Instruction::SetGasPrice { gas_price, outcome_sender, } => { - evm.env.tx.gas_price = U256::from_limbs(gas_price.0); - outcome_sender - .send(Ok(Outcome::SetGasPriceCompleted)) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; + evm.tx_mut().gas_price = U256::from_limbs(gas_price.0); + outcome_sender.send(Ok(Outcome::SetGasPriceCompleted))?; } // A `Transaction` is state changing and will create events. @@ -559,44 +436,28 @@ impl Environment { outcome_sender, } => { // Set the tx_env and prepare to process it - evm.env.tx = tx_env; + *evm.tx_mut() = tx_env; - let execution_result = match evm.inspect_commit(&mut inspector) { + let execution_result = match evm.transact_commit() { Ok(result) => { - if let Some(inspector) = &mut inspector.console_log { - inspector.0.drain(..).for_each(|log| { + if let Some(console_log) = &mut evm.context.external.console_log { + console_log.0.drain(..).for_each(|log| { trace!( "Console logs: {:?}", - console::abi::HardhatConsoleCalls::decode(log) - .unwrap() - .to_string() + HardhatConsoleCalls::decode(log).unwrap().to_string() ) }); }; result } Err(e) => { - if let EVMError::Transaction(invalid_transaction) = e { - outcome_sender - .send(Err(EnvironmentError::Transaction( - invalid_transaction, - ))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; - continue; - } else { - outcome_sender - .send(Err(EnvironmentError::Execution(e))) - .map_err(|e| { - EnvironmentError::Communication(e.to_string()) - })?; - continue; - } + outcome_sender.send(Err(ArbiterCoreError::EVMError(e)))?; + continue; } }; - cumulative_gas_per_block += U256::from(execution_result.clone().gas_used()); - let block_number = convert_uint_to_u64(evm.env.block.number)?; + cumulative_gas_per_block += + eU256::from(execution_result.clone().gas_used()); + let block_number = convert_uint_to_u64(evm.block().number)?; let receipt_data = ReceiptData { block_number, transaction_index, @@ -610,12 +471,10 @@ impl Environment { ) } } - outcome_sender - .send(Ok(Outcome::TransactionCompleted( - execution_result, - receipt_data, - ))) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; + outcome_sender.send(Ok(Outcome::TransactionCompleted( + execution_result, + receipt_data, + )))?; transaction_index += U64::from(1); } @@ -625,56 +484,48 @@ impl Environment { } => { let outcome = match environment_data { EnvironmentData::BlockNumber => { - Ok(Outcome::QueryReturn(evm.env.block.number.to_string())) + Ok(Outcome::QueryReturn(evm.block().number.to_string())) } EnvironmentData::BlockTimestamp => { - Ok(Outcome::QueryReturn(evm.env.block.timestamp.to_string())) + Ok(Outcome::QueryReturn(evm.block().timestamp.to_string())) } EnvironmentData::GasPrice => { - Ok(Outcome::QueryReturn(evm.env.tx.gas_price.to_string())) + Ok(Outcome::QueryReturn(evm.tx().gas_price.to_string())) } EnvironmentData::Balance(address) => { // This unwrap should never fail. - let db = evm.db().unwrap(); + let db = &mut evm.context.evm.db; match db .0 - .write() + .read() .unwrap() .accounts - .get::( - &address.as_fixed_bytes().into(), - ) { + .get::
(&address.as_fixed_bytes().into()) + { Some(account) => { Ok(Outcome::QueryReturn(account.info.balance.to_string())) } - None => Err(EnvironmentError::Account( - "Account is missing!".to_string(), - )), + None => Err(ArbiterCoreError::AccountDoesNotExistError), } } EnvironmentData::TransactionCount(address) => { - let db = evm.db().unwrap(); + let db = &mut evm.context.evm.db; match db .0 - .write() + .read() .unwrap() .accounts - .get::( - &address.as_fixed_bytes().into(), - ) { + .get::
(&address.as_fixed_bytes().into()) + { Some(account) => { Ok(Outcome::QueryReturn(account.info.nonce.to_string())) } - None => Err(EnvironmentError::Account( - "Account is missing!".to_string(), - )), + None => Err(ArbiterCoreError::AccountDoesNotExistError), } } }; - outcome_sender - .send(outcome) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; + outcome_sender.send(outcome)?; } Instruction::Stop(outcome_sender) => { match event_broadcaster.send(Broadcast::StopSignal) { @@ -683,9 +534,8 @@ impl Environment { warn!("Stop signal was not sent to any listeners. Are there any listeners?") } } - outcome_sender - .send(Ok(Outcome::StopCompleted(evm.db.unwrap()))) - .map_err(|e| EnvironmentError::Communication(e.to_string()))?; + let (db, _) = evm.into_db_and_env_with_handler_cfg(); + outcome_sender.send(Ok(Outcome::StopCompleted(db)))?; break; } } @@ -696,33 +546,18 @@ impl Environment { self } - /// Stops the execution of the environment. - /// This cannot be recovered from! - /// - /// # Returns - /// - /// * `Ok(())` if the environment was successfully stopped or was already - /// stopped. - /// * `Err(EnvironmentError::Stop(String))` if the environment is in an - /// invalid state. - pub fn stop(mut self) -> Result, EnvironmentError> { + /// Stops the execution of the environment and returns the [`ArbiterDB`] in + /// its final state. + pub fn stop(mut self) -> Result { let (outcome_sender, outcome_receiver) = bounded(1); self.socket .instruction_sender - .send(Instruction::Stop(outcome_sender)) - .map_err(|e| { - EnvironmentError::Stop(format!( - "Stop request failed to send due to {:?}.\nIs the environment already stopped?", - e - )) - })?; - let outcome = outcome_receiver - .recv() - .map_err(|e| EnvironmentError::Communication(e.to_string()))??; + .send(Instruction::Stop(outcome_sender))?; + let outcome = outcome_receiver.recv()??; let db = match outcome { - Outcome::StopCompleted(stopped_db) => Some(stopped_db), - _ => return Err(EnvironmentError::Stop("Failed to stop environment!".into())), + Outcome::StopCompleted(stopped_db) => stopped_db, + _ => unreachable!(), }; if let Some(label) = &self.parameters.label { @@ -733,13 +568,9 @@ impl Environment { drop(self.socket.instruction_sender); self.handle .take() - .ok_or(EnvironmentError::Stop( - "failed to join the environment handle!".to_owned(), - ))? + .unwrap() .join() - .map_err(|_| { - EnvironmentError::Stop("Failed to join environment handle.".to_owned()) - })??; + .map_err(|_| ArbiterCoreError::JoinError)??; Ok(db) } } @@ -778,12 +609,51 @@ pub enum Broadcast { /// * `Ok(U64)` - The converted U64. /// Used for block number which is a U64. #[inline] -fn convert_uint_to_u64(input: U256) -> Result { +fn convert_uint_to_u64(input: U256) -> Result { let as_str = input.to_string(); match as_str.parse::() { Ok(val) => Ok(val.into()), - Err(_) => Err(EnvironmentError::Conversion( - "U256 value is too large to fit into u64".to_string(), - )), + Err(e) => Err(e)?, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub(crate) const TEST_ENV_LABEL: &str = "test"; + const TEST_CONTRACT_SIZE_LIMIT: usize = 42069; + const TEST_GAS_LIMIT: u64 = 1_333_333_333_337; + + #[test] + fn new_with_parameters() { + let environment = Environment::builder() + .with_label(TEST_ENV_LABEL) + .with_contract_size_limit(TEST_CONTRACT_SIZE_LIMIT) + .with_gas_limit(U256::from(TEST_GAS_LIMIT)); + assert_eq!(environment.parameters.label, Some(TEST_ENV_LABEL.into())); + assert_eq!( + environment.parameters.contract_size_limit.unwrap(), + TEST_CONTRACT_SIZE_LIMIT + ); + assert_eq!( + environment.parameters.gas_limit.unwrap(), + U256::from(TEST_GAS_LIMIT) + ); + } + + #[test] + fn 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()); } } diff --git a/arbiter-core/src/environment/tests.rs b/arbiter-core/src/environment/tests.rs deleted file mode 100644 index b4e9696a2..000000000 --- a/arbiter-core/src/environment/tests.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::*; - -pub(crate) const TEST_ENV_LABEL: &str = "test"; -const TEST_CONTRACT_SIZE_LIMIT: usize = 42069; -const TEST_GAS_LIMIT: u64 = 1_333_333_333_337; - -#[test] -fn new_with_parameters() { - let environment = Environment::builder() - .with_label(TEST_ENV_LABEL) - .with_contract_size_limit(TEST_CONTRACT_SIZE_LIMIT) - .with_gas_limit(U256::from(TEST_GAS_LIMIT)); - assert_eq!(environment.parameters.label, Some(TEST_ENV_LABEL.into())); - assert_eq!( - environment.parameters.contract_size_limit.unwrap(), - TEST_CONTRACT_SIZE_LIMIT - ); - assert_eq!( - environment.parameters.gas_limit.unwrap(), - U256::from(TEST_GAS_LIMIT) - ); -} - -#[test] -fn 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()); -} diff --git a/arbiter-core/src/errors.rs b/arbiter-core/src/errors.rs new file mode 100644 index 000000000..3a67728a9 --- /dev/null +++ b/arbiter-core/src/errors.rs @@ -0,0 +1,122 @@ +//! Errors that can occur when managing or interfacing with Arbiter's sandboxed +//! Ethereum environment. + +// use crossbeam_channel::SendError; +use crossbeam_channel::{RecvError, SendError}; +use ethers::{ + providers::{MiddlewareError, ProviderError}, + signers::WalletError, +}; +use revm_primitives::{EVMError, HaltReason}; +use thiserror::Error; + +use self::environment::instruction::{Instruction, Outcome}; +use super::*; + +/// The error type for `arbiter-core`. +#[derive(Error, Debug)] +pub enum ArbiterCoreError { + /// Tried to create an account that already exists. + #[error("Account already exists!")] + AccountCreationError, + + /// Tried to access an account that doesn't exist. + #[error("Account doesn't exist!")] + AccountDoesNotExistError, + + /// Tried to sign with forked EOA. + #[error("Can't sign with a forked EOA!")] + ForkedEOASignError, + + /// Failed to upgrade instruction sender in middleware. + #[error("Failed to upgrade sender to a strong reference!")] + UpgradeSenderError, + + /// Data missing when calling a transaction. + #[error("Data missing when calling a transaction!")] + MissingDataError, + + /// Invalid data used for a query request. + #[error("Invalid data used for a query request!")] + InvalidQueryError, + + /// Failed to join environment thread on stop. + #[error("Failed to join environment thread on stop!")] + JoinError, + + /// Reverted execution. + #[error("Execution failed with revert: {gas_used:?} gas used, {output:?}")] + ExecutionRevert { + /// The amount of gas used. + gas_used: u64, + /// The output bytes of the execution. + output: Vec, + }, + + /// Halted execution. + #[error("Execution failed with halt: {reason:?}, {gas_used:?} gas used")] + ExecutionHalt { + /// The halt reason. + reason: HaltReason, + /// The amount of gas used. + gas_used: u64, + }, + + /// Failed to parse integer. + #[error(transparent)] + ParseIntError(#[from] std::num::ParseIntError), + + /// Evm had a runtime error. + #[error(transparent)] + EVMError(#[from] EVMError), + + /// Provider error. + #[error(transparent)] + ProviderError(#[from] ProviderError), + + /// Wallet error. + #[error(transparent)] + WalletError(#[from] WalletError), + + /// Send error. + #[error(transparent)] + SendError( + #[from] + #[allow(private_interfaces)] + SendError, + ), + + /// Recv error. + #[error(transparent)] + RecvError(#[from] RecvError), + + /// Failed to parse integer from string. + #[error(transparent)] + FromStrRadixError(#[from] uint::FromStrRadixErr), + + /// Failed to handle json. + #[error(transparent)] + SerdeJsonError(#[from] serde_json::Error), + + /// Failed to reply to instruction. + #[error("{0}")] + ReplyError(String), +} + +impl From>> for ArbiterCoreError { + fn from(e: SendError>) -> Self { + ArbiterCoreError::ReplyError(e.to_string()) + } +} + +impl MiddlewareError for ArbiterCoreError { + type Inner = ProviderError; + + fn from_err(e: Self::Inner) -> Self { + ArbiterCoreError::from(e) + } + + fn as_inner(&self) -> Option<&Self::Inner> { + None + } +} diff --git a/arbiter-core/src/lib.rs b/arbiter-core/src/lib.rs index 9ab7d708d..217cd8832 100644 --- a/arbiter-core/src/lib.rs +++ b/arbiter-core/src/lib.rs @@ -16,8 +16,6 @@ //! without associated overheads like networking latency. //! //! Key Features: -//! - **Manager Interface**: The main user entry-point that offers management of -//! different environments and agents. //! - **Environment Handling**: Detailed setup and control mechanisms for //! running the Ethereum-like blockchain environment. //! - **Middleware Implementation**: Customized middleware to reduce overhead @@ -36,9 +34,27 @@ pub mod coprocessor; pub mod data_collection; pub mod database; pub mod environment; -pub mod inspector; +pub mod errors; pub mod middleware; -#[cfg(test)] -mod tests; +use std::{ + collections::{BTreeMap, HashMap}, + convert::Infallible, + fmt::Debug, + ops::Range, + sync::{Arc, RwLock}, +}; + +use async_trait::async_trait; +use ethers::types::{Address as eAddress, Filter, H256, U256 as eU256, U64}; +use revm::{ + db::{CacheDB, EmptyDB}, + interpreter::{CallInputs, CallOutcome}, + primitives::{AccountInfo, Address, Bytes, ExecutionResult, Log, TxEnv, U256}, + Database, Evm, EvmContext, Inspector, +}; +use serde::{Deserialize, Serialize}; +use tokio::sync::broadcast::{Receiver as BroadcastReceiver, Sender as BroadcastSender}; use tracing::{debug, error, info, trace, warn}; + +use crate::{database::ArbiterDB, environment::Broadcast, errors::ArbiterCoreError}; diff --git a/arbiter-core/src/middleware/cast.rs b/arbiter-core/src/middleware/cast.rs deleted file mode 100644 index cd5e20bd0..000000000 --- a/arbiter-core/src/middleware/cast.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Utility functions for casting between revm and ethers-rs types. -use super::*; - -/// Converts logs from the Revm format to the Ethers format. -/// -/// This function iterates over a list of logs as they appear in the `revm` and -/// converts each log entry to the corresponding format used by the `ethers-rs` -/// library. -#[inline] -pub fn revm_logs_to_ethers_logs( - revm_logs: Vec, -) -> Vec { - let mut logs: Vec = vec![]; - for revm_log in revm_logs { - let topics = revm_log.topics.into_iter().map(recast_b256).collect(); - let log = ethers::core::types::Log { - address: ethers::core::types::H160::from(revm_log.address.into_array()), - topics, - data: ethers::core::types::Bytes::from(revm_log.data.0), - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - transaction_log_index: None, - log_type: None, - removed: None, - }; - logs.push(log); - } - logs -} - -// Certainly will go away with alloy-types -/// Recast a B160 into an Address type -/// # Arguments -/// * `address` - B160 to recast. (B160) -/// # Returns -/// * `Address` - Recasted Address. -#[inline] -pub fn recast_address(address: revm::primitives::Address) -> Address { - Address::from(address.into_array()) -} - -/// Recast a B256 into an H256 type -/// # Arguments -/// * `input` - B256 to recast. (B256) -/// # Returns -/// * `H256` - Recasted H256. -#[inline] -pub fn recast_b256(input: revm::primitives::B256) -> ethers::types::H256 { - ethers::types::H256::from(input.0) -} diff --git a/arbiter-core/src/middleware/connection.rs b/arbiter-core/src/middleware/connection.rs index f4c11e95b..19e046285 100644 --- a/arbiter-core/src/middleware/connection.rs +++ b/arbiter-core/src/middleware/connection.rs @@ -1,16 +1,7 @@ //! Messengers/connections to the underlying EVM in the environment. -use std::{ - collections::HashMap, - fmt::Debug, - pin::Pin, - sync::{Arc, Weak}, -}; - -use futures_util::Stream; -use serde_json::value::RawValue; -use tokio::sync::broadcast::{Receiver as BroadcastReceiver, Sender as BroadcastSender}; - -use super::{cast::revm_logs_to_ethers_logs, *}; +use std::sync::Weak; + +use super::*; use crate::environment::{InstructionSender, OutcomeReceiver, OutcomeSender}; /// Represents a connection to the EVM contained in the corresponding @@ -221,3 +212,45 @@ pub(crate) struct FilterReceiver { /// These are filtered upon reception. pub(crate) receiver: Option>, } + +// TODO: The logs below could have the block number, transaction index, and +// maybe other fields populated. + +/// Converts logs from the Revm format to the Ethers format. +/// +/// This function iterates over a list of logs as they appear in the `revm` and +/// converts each log entry to the corresponding format used by the `ethers-rs` +/// library. +#[inline] +pub fn revm_logs_to_ethers_logs(revm_logs: Vec) -> Vec { + let mut logs: Vec = vec![]; + for revm_log in revm_logs { + let topics = revm_log.topics().iter().map(recast_b256).collect(); + let data = ethers::core::types::Bytes::from(revm_log.data.data.0); + let log = ethers::core::types::Log { + address: ethers::core::types::H160::from(revm_log.address.into_array()), + topics, + data, + block_hash: None, + block_number: None, + transaction_hash: None, + transaction_index: None, + log_index: None, + transaction_log_index: None, + log_type: None, + removed: None, + }; + logs.push(log); + } + logs +} + +/// Recast a B256 into an H256 type +/// # Arguments +/// * `input` - B256 to recast. (B256) +/// # Returns +/// * `H256` - Recasted H256. +#[inline] +pub fn recast_b256(input: &revm::primitives::B256) -> ethers::types::H256 { + ethers::types::H256::from(input.0) +} diff --git a/arbiter-core/src/middleware/errors.rs b/arbiter-core/src/middleware/errors.rs deleted file mode 100644 index bc7c1ff4e..000000000 --- a/arbiter-core/src/middleware/errors.rs +++ /dev/null @@ -1,88 +0,0 @@ -use super::*; - -/// Possible errors thrown by interacting with the revm middleware client. -/// Errors that can occur while using the [`RevmMiddleware`]. -/// These errors are likely to be more common than other errors in -/// `arbiter-core` as they can come from simple issues such as contract reverts -/// or halts. Certain errors such as [`RevmMiddlewareError::Send`], -/// [`RevmMiddlewareError::Receive`], [`RevmMiddlewareError::Conversion`], -/// [`RevmMiddlewareError::Json`], and [`RevmMiddlewareError::EventBroadcaster`] -/// are considered more worrying. If these are achieved, please feel free to -/// contact our team via the [Telegram group](https://t.me/arbiter_rs) or on -/// [GitHub](https://github.com/primitivefinance/arbiter/). -#[derive(Error, Debug)] -pub enum RevmMiddlewareError { - /// An error occurred while attempting to interact with the [`Environment`]. - #[error("an error came from the environment! due to: {0}")] - Environment(#[from] crate::environment::errors::EnvironmentError), - - /// An error occurred while attempting to interact with the provider: - /// [`Connection`]. - #[error("an error came from the provider! due to: {0}")] - Provider(#[from] ProviderError), - - /// An error occurred while attempting to send a transaction. - #[error("failed to send transaction! due to: {0}")] - Send(String), - - /// There was an issue receiving an [`ExecutionResult`], possibly from - /// another service or module. - #[error("failed to receive `ExecutionResult`! due to: {0}")] - Receive(#[from] crossbeam_channel::RecvError), - - /// There was a failure trying to obtain a lock on the [`EventBroadcaster`], - /// possibly due to concurrency issues. - #[error("failed to gain event broadcaster lock! due to: {0}")] - EventBroadcaster(String), - - /// The required data or functionality for an instruction was missing or - /// incomplete. - #[error("missing data! due to: {0}")] - MissingData(String), - - /// An error occurred during type conversion, possibly when translating - /// between domain-specific types. - #[error("failed to convert types! due to: {0}")] - Conversion(String), - - /// An error occurred while trying to serialize or deserialize JSON data. - #[error("failed to handle with JSON data! due to: {0:?}")] - Json(serde_json::Error), - - /// The execution of a transaction was reverted, indicating that the - /// transaction was not successful. - #[error("execution failed to succeed due to revert!\n gas used is: {gas_used}\n output is {output:?}")] - ExecutionRevert { - /// Provides the amount of gas used by the transaction. - gas_used: u64, - - /// Provides the output or reason why the transaction was reverted. - output: revm::primitives::Bytes, - }, - - /// The execution of a transaction halted unexpectedly. - #[error("execution failed to succeed due to halt!\n reason is: {reason:?}\n gas used is: {gas_used}")] - ExecutionHalt { - /// Provides the reason for the halt. - reason: revm::primitives::Halt, - - /// Provides the amount of gas used by the transaction. - gas_used: u64, - }, - - /// There was an error with a signature. - #[error("signature error! due to: {0}")] - Signing(String), -} - -impl MiddlewareError for RevmMiddlewareError { - type Inner = ProviderError; - - fn from_err(e: Self::Inner) -> Self { - RevmMiddlewareError::Provider(e) - } - - fn as_inner(&self) -> Option<&Self::Inner> { - None - } -} diff --git a/arbiter-core/src/middleware/mod.rs b/arbiter-core/src/middleware/mod.rs index 531b94998..7e3468e23 100644 --- a/arbiter-core/src/middleware/mod.rs +++ b/arbiter-core/src/middleware/mod.rs @@ -1,23 +1,15 @@ -//! The `middleware` module provides functionality to interact with +//! The [`middleware`] module provides functionality to interact with //! Ethereum-like virtual machines. It achieves this by offering a middleware //! implementation for sending and reading transactions, as well as watching //! for events. //! //! Main components: -//! - [`RevmMiddleware`]: The core middleware implementation. -//! - [`RevmMiddlewareError`]: Error type for the middleware. +//! - [`ArbiterMiddleware`]: The core middleware implementation. //! - [`Connection`]: Handles communication with the Ethereum VM. -//! - `FilterReceiver`: Facilitates event watching based on certain filters. +//! - [`FilterReceiver`]: Facilitates event watching based on certain filters. #![warn(missing_docs)] -use std::{ - collections::HashMap, - fmt::Debug, - future::Future, - pin::Pin, - sync::{Arc, Mutex}, - time::Duration, -}; +use std::{future::Future, pin::Pin, sync::Mutex, time::Duration}; use ethers::{ abi::ethereum_types::BloomInput, @@ -29,74 +21,58 @@ use ethers::{ ProviderError, }, providers::{ - FilterKind, FilterWatcher, JsonRpcClient, Middleware, MiddlewareError, PendingTransaction, - Provider, PubsubClient, SubscriptionStream, + FilterKind, FilterWatcher, JsonRpcClient, Middleware, PendingTransaction, Provider, + PubsubClient, SubscriptionStream, }, signers::{Signer, Wallet}, types::{ transaction::{eip2718::TypedTransaction, eip712::Eip712}, - Address, BlockId, Bloom, Bytes, Filter, FilteredParams, Log, NameOrAddress, Signature, - Transaction, TransactionReceipt, U256 as eU256, U64, + Address as eAddress, BlockId, Bloom, Bytes as eBytes, FilteredParams, Log as eLog, + NameOrAddress, Signature, Transaction, TransactionReceipt, TxHash as eTxHash, }, }; use futures_timer::Delay; use futures_util::Stream; use rand::{rngs::StdRng, SeedableRng}; -use revm::primitives::{CreateScheme, Output, TransactTo, TxEnv, U256}; -use serde::{de::DeserializeOwned, Serialize}; +use revm::primitives::{CreateScheme, Output, TransactTo}; +use serde::de::DeserializeOwned; use serde_json::value::RawValue; -use thiserror::Error; use super::*; -use crate::environment::{cheatcodes::*, instruction::*, Broadcast, Environment}; - -/// Possible errors thrown by interacting with the revm middleware client. -pub mod errors; -use errors::*; - -/// Graceful handling of the [`ExecutionResult`] returned by the [`Environment`] -pub mod transaction; -use transaction::*; +use crate::environment::{instruction::*, Broadcast, Environment}; pub mod connection; use connection::*; -pub mod cast; -use cast::*; - pub mod nonce_middleware; /// A middleware structure that integrates with `revm`. /// -/// [`RevmMiddleware`] serves as a bridge between the application and `revm`'s -/// execution environment, allowing for transaction sending, call execution, and -/// other core functions. It uses a custom connection and error system tailored -/// to Revm's specific needs. +/// [`ArbiterMiddleware`] serves as a bridge between the application and +/// [`revm`]'s execution environment, allowing for transaction sending, call +/// execution, and other core functions. It uses a custom connection and error +/// system tailored to Revm's specific needs. /// -/// This allows for `revm` and the [`Environment`] built around it to be treated -/// in much the same way as a live EVM blockchain can be addressed. +/// This allows for [`revm`] and the [`Environment`] built around it to be +/// treated in much the same way as a live EVM blockchain can be addressed. /// /// # Examples /// /// Basic usage: /// ``` -/// // Get the necessary dependencies -/// // Import `Arc` if you need to create a client instance -/// use std::sync::Arc; -/// -/// use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; +/// use arbiter_core::{environment::Environment, middleware::ArbiterMiddleware}; /// /// // Create a new environment and run it /// let mut environment = Environment::builder().build(); /// /// // Retrieve the environment to create a new middleware instance -/// let middleware = RevmMiddleware::new(&environment, Some("test_label")); +/// let middleware = ArbiterMiddleware::new(&environment, Some("test_label")); /// ``` /// The client can now be used for transactions with the environment. /// Use a seed like `Some("test_label")` for maintaining a /// consistent address across simulations and client labeling. Seeding is be /// useful for debugging and post-processing. #[derive(Debug)] -pub struct RevmMiddleware { +pub struct ArbiterMiddleware { provider: Provider, wallet: EOA, /// An optional label for the middleware instance @@ -104,25 +80,20 @@ pub struct RevmMiddleware { pub label: Option, } -#[async_trait::async_trait] -impl Signer for RevmMiddleware { - type Error = RevmMiddlewareError; +#[async_trait] +impl Signer for ArbiterMiddleware { + type Error = ArbiterCoreError; async fn sign_message>( &self, message: S, ) -> Result { match self.wallet { - EOA::Forked(_) => Err(RevmMiddlewareError::Signing( - "Cannot sign messages with a forked EOA!".to_string(), - )), + EOA::Forked(_) => Err(ArbiterCoreError::ForkedEOASignError), EOA::Wallet(ref wallet) => { let message = message.as_ref(); let message_hash = ethers::utils::hash_message(message); - let signature = wallet - .sign_message(message_hash) - .await - .map_err(|e| RevmMiddlewareError::Signing(format!("Signing error: {}", e)))?; + let signature = wallet.sign_message(message_hash).await?; Ok(signature) } } @@ -131,14 +102,9 @@ impl Signer for RevmMiddleware { /// Signs the transaction async fn sign_transaction(&self, message: &TypedTransaction) -> Result { match self.wallet { - EOA::Forked(_) => Err(RevmMiddlewareError::Signing( - "Cannot sign transactions with a forked EOA!".to_string(), - )), + EOA::Forked(_) => Err(ArbiterCoreError::ForkedEOASignError), EOA::Wallet(ref wallet) => { - let signature = wallet - .sign_transaction(message) - .await - .map_err(|e| RevmMiddlewareError::Signing(format!("Signing error: {}", e)))?; + let signature = wallet.sign_transaction(message).await?; Ok(signature) } } @@ -151,21 +117,16 @@ impl Signer for RevmMiddleware { payload: &T, ) -> Result { match self.wallet { - EOA::Forked(_) => Err(RevmMiddlewareError::Signing( - "Cannot sign typed data with a forked EOA!".to_string(), - )), + EOA::Forked(_) => Err(ArbiterCoreError::ForkedEOASignError), EOA::Wallet(ref wallet) => { - let signature = wallet - .sign_typed_data(payload) - .await - .map_err(|e| RevmMiddlewareError::Signing(format!("Signing error: {}", e)))?; + let signature = wallet.sign_typed_data(payload).await?; Ok(signature) } } } /// Returns the signer's Ethereum Address - fn address(&self) -> Address { + fn address(&self) -> eAddress { match &self.wallet { EOA::Forked(address) => *address, EOA::Wallet(wallet) => wallet.address(), @@ -191,7 +152,7 @@ impl Signer for RevmMiddleware { } #[async_trait::async_trait] -impl JsonRpcClient for RevmMiddleware { +impl JsonRpcClient for ArbiterMiddleware { type Error = ProviderError; async fn request( &self, @@ -203,7 +164,7 @@ impl JsonRpcClient for RevmMiddleware { } #[async_trait::async_trait] -impl PubsubClient for RevmMiddleware { +impl PubsubClient for ArbiterMiddleware { type NotificationStream = Pin> + Send>>; fn subscribe>( @@ -225,40 +186,36 @@ pub enum EOA { /// The [`Forked`] variant is used for the forked EOA, /// allowing us to treat them as mock accounts that we can still authorize /// transactions with that we would be unable to do on mainnet. - Forked(Address), + Forked(eAddress), /// The [`Wallet`] variant "real" in the sense that is has a valid private /// key from the provided seed Wallet(Wallet), } -impl RevmMiddleware { - /// Creates a new instance of `RevmMiddleware` with procedurally generated - /// signer/address if provided a seed/label and otherwise a random - /// signer if not. +impl ArbiterMiddleware { + /// Creates a new instance of `ArbiterMiddleware` with procedurally + /// generated signer/address if provided a seed/label and otherwise a + /// random signer if not. /// /// # Examples /// ``` - /// // Get the necessary dependencies - /// // Import `Arc` if you need to create a client instance - /// use std::sync::Arc; - /// - /// use arbiter_core::{environment::Environment, middleware::RevmMiddleware}; + /// use arbiter_core::{environment::Environment, middleware::ArbiterMiddleware}; /// /// // Create a new environment and run it /// let mut environment = Environment::builder().build(); /// /// // Retrieve the environment to create a new middleware instance - /// let client = RevmMiddleware::new(&environment, Some("test_label")); + /// let client = ArbiterMiddleware::new(&environment, Some("test_label")); /// /// // We can create a middleware instance without a seed by doing the following - /// let no_seed_middleware = RevmMiddleware::new(&environment, None); + /// let no_seed_middleware = ArbiterMiddleware::new(&environment, None); /// ``` /// Use a seed if you want to have a constant address across simulations as /// well as a label for a client. This can be useful for debugging. pub fn new( environment: &Environment, seed_and_label: Option<&str>, - ) -> Result, RevmMiddlewareError> { + ) -> Result, ArbiterCoreError> { let connection = Connection::from(environment); let wallet = if let Some(seed) = seed_and_label { let mut hasher = Sha256::new(); @@ -273,19 +230,16 @@ impl RevmMiddleware { connection .instruction_sender .upgrade() - .ok_or(errors::RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - ))? + .ok_or(ArbiterCoreError::UpgradeSenderError)? .send(Instruction::AddAccount { address: wallet.address(), outcome_sender: connection.outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; + })?; connection.outcome_receiver.recv()??; let provider = Provider::new(connection); info!( - "Created new `RevmMiddleware` instance attached to environment labeled: + "Created new `ArbiterMiddleware` instance attached to environment labeled: {:?}", environment.parameters.label ); @@ -297,11 +251,11 @@ impl RevmMiddleware { } // TODO: This needs to have the label retrieved from the fork config. - /// Creates a new instance of `RevmMiddleware` from a forked EOA. + /// Creates a new instance of `ArbiterMiddleware` from a forked EOA. pub fn new_from_forked_eoa( environment: &Environment, - forked_eoa: Address, - ) -> Result, RevmMiddlewareError> { + forked_eoa: eAddress, + ) -> Result, ArbiterCoreError> { let instruction_sender = &Arc::clone(&environment.socket.instruction_sender); let (outcome_sender, outcome_receiver) = crossbeam_channel::unbounded(); @@ -314,7 +268,7 @@ impl RevmMiddleware { }; let provider = Provider::new(connection); info!( - "Created new `RevmMiddleware` instance from a fork -- attached to environment labeled: {:?}", + "Created new `ArbiterMiddleware` instance from a fork -- attached to environment labeled: {:?}", environment.parameters.label ); Ok(Arc::new(Self { @@ -328,59 +282,43 @@ impl RevmMiddleware { /// [`Environment`] to whatever they may choose at any time. pub fn update_block( &self, - block_number: impl Into, - block_timestamp: impl Into, - ) -> Result { - let block_number: ethers::types::U256 = block_number.into(); - let block_timestamp: ethers::types::U256 = block_timestamp.into(); + block_number: impl Into, + block_timestamp: impl Into, + ) -> Result { let provider = self.provider().as_ref(); - if let Some(instruction_sender) = provider.instruction_sender.upgrade() { - instruction_sender - .send(Instruction::BlockUpdate { - block_number: revm_primitives::FixedBytes::<32>(block_number.into()).into(), - block_timestamp: revm_primitives::FixedBytes::<32>(block_timestamp.into()) - .into(), - outcome_sender: provider.outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - match provider.outcome_receiver.recv() { - Ok(Ok(Outcome::BlockUpdateCompleted(receipt_data))) => { - debug!("Block update applied"); - Ok(receipt_data) - } - _ => Err(RevmMiddlewareError::MissingData( - "Block did not update Successfully".to_string(), - )), - } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::BlockUpdate { + block_number: block_number.into(), + block_timestamp: block_timestamp.into(), + outcome_sender: provider.outcome_sender.clone(), + })?; + + match provider.outcome_receiver.recv()?? { + Outcome::BlockUpdateCompleted(receipt_data) => Ok(receipt_data), + _ => unreachable!(), } } /// Returns the timestamp of the current block. - pub async fn get_block_timestamp(&self) -> Result { - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(Instruction::Query { - environment_data: EnvironmentData::BlockTimestamp, - outcome_sender: self.provider().as_ref().outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - match self.provider().as_ref().outcome_receiver.recv()?? { - Outcome::QueryReturn(outcome) => { - ethers::types::U256::from_str_radix(outcome.as_ref(), 10) - .map_err(|e| RevmMiddlewareError::Conversion(e.to_string())) - } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via query!".to_string(), - )), + pub async fn get_block_timestamp(&self) -> Result { + let provider = self.provider().as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::Query { + environment_data: EnvironmentData::BlockTimestamp, + outcome_sender: provider.outcome_sender.clone(), + })?; + + match provider.outcome_receiver.recv()?? { + Outcome::QueryReturn(outcome) => { + Ok(ethers::types::U256::from_str_radix(outcome.as_ref(), 10)?) } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + _ => unreachable!(), } } @@ -388,34 +326,26 @@ impl RevmMiddleware { pub async fn apply_cheatcode( &self, cheatcode: Cheatcodes, - ) -> Result { - if let Some(instruction_sender) = self.provider.as_ref().instruction_sender.upgrade() { - instruction_sender - .send(Instruction::Cheatcode { - cheatcode, - outcome_sender: self.provider().as_ref().outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - - match self.provider().as_ref().outcome_receiver.recv()?? { - Outcome::CheatcodeReturn(outcome) => { - debug!("Cheatcode applied"); - Ok(outcome) - } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via instruction outcome!".to_string(), - )), - } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + ) -> Result { + let provider = self.provider.as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::Cheatcode { + cheatcode, + outcome_sender: provider.outcome_sender.clone(), + })?; + + match provider.outcome_receiver.recv()?? { + Outcome::CheatcodeReturn(outcome) => Ok(outcome), + _ => unreachable!(), } } /// Returns the address of the wallet/signer given to a client. - /// Matches on the [`EOA`] variant of the [`RevmMiddleware`] struct. - pub fn address(&self) -> Address { + /// Matches on the [`EOA`] variant of the [`ArbiterMiddleware`] struct. + pub fn address(&self) -> eAddress { match &self.wallet { EOA::Forked(address) => *address, EOA::Wallet(wallet) => wallet.address(), @@ -429,52 +359,47 @@ impl RevmMiddleware { pub async fn set_gas_price( &self, gas_price: ethers::types::U256, - ) -> Result<(), RevmMiddlewareError> { - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(Instruction::SetGasPrice { - gas_price, - outcome_sender: self.provider().as_ref().outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - match self.provider().as_ref().outcome_receiver.recv()?? { - Outcome::SetGasPriceCompleted => { - debug!("Gas price set"); - Ok(()) - } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via instruction outcome!".to_string(), - )), + ) -> Result<(), ArbiterCoreError> { + let provider = self.provider.as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::SetGasPrice { + gas_price, + outcome_sender: provider.outcome_sender.clone(), + })?; + match provider.outcome_receiver.recv()?? { + Outcome::SetGasPriceCompleted => { + debug!("Gas price set"); + Ok(()) } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + _ => unreachable!(), } } } #[async_trait::async_trait] -impl Middleware for RevmMiddleware { +impl Middleware for ArbiterMiddleware { type Provider = Connection; - type Error = RevmMiddlewareError; + type Error = ArbiterCoreError; type Inner = Provider; /// Returns a reference to the inner middleware of which there is none when - /// using [`RevmMiddleware`] so we relink to `Self` + /// using [`ArbiterMiddleware`] so we relink to `Self` fn inner(&self) -> &Self::Inner { &self.provider } /// Provides access to the associated Ethereum provider which is given by - /// the [`Provider`] for [`RevmMiddleware`]. + /// the [`Provider`] for [`ArbiterMiddleware`]. fn provider(&self) -> &Provider { &self.provider } /// Provides the default sender address for transactions, i.e., the address /// of the wallet/signer given to a client of the [`Environment`]. - fn default_sender(&self) -> Option
{ + fn default_sender(&self) -> Option { Some(self.address()) } @@ -510,9 +435,7 @@ impl Middleware for RevmMiddleware { value: U256::ZERO, data: revm_primitives::Bytes(bytes::Bytes::from( tx.data() - .ok_or(RevmMiddlewareError::MissingData( - "Data missing in transaction!".to_string(), - ))? + .ok_or(ArbiterCoreError::MissingDataError)? .to_vec(), )), chain_id: None, @@ -526,153 +449,163 @@ impl Middleware for RevmMiddleware { outcome_sender: self.provider.as_ref().outcome_sender.clone(), }; - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(instruction) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - } else { - return Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )); - } + let provider = self.provider.as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(instruction)?; - let outcome = self.provider().as_ref().outcome_receiver.recv()??; + let outcome = provider.outcome_receiver.recv()??; if let Outcome::TransactionCompleted(execution_result, receipt_data) = outcome { - let Success { - _reason: _, - _gas_used: gas_used, - _gas_refunded: _, - logs, - output, - } = unpack_execution_result(execution_result)?; - - let to: Option = match tx_env.transact_to { - TransactTo::Call(address) => Some(address.into_array().into()), - TransactTo::Create(_) => None, - }; - - // Note that this is technically not the correct construction on the tx hash - // but until we increment the nonce correctly this will do - let sender = self.address(); - let data = tx_env.clone().data; - let mut hasher = Sha256::new(); - hasher.update(sender.as_bytes()); - hasher.update(data.as_ref()); - let hash = hasher.finalize(); - - let mut block_hasher = Sha256::new(); - block_hasher.update(receipt_data.block_number.to_string().as_bytes()); - let block_hash = block_hasher.finalize(); - let block_hash = Some(ethers::types::H256::from_slice(&block_hash)); - - match output { - Output::Create(_, address) => { - let tx_receipt = TransactionReceipt { - block_hash, - block_number: Some(receipt_data.block_number), - contract_address: Some(recast_address(address.unwrap())), - logs: logs.clone(), - from: sender, - gas_used: Some(gas_used.into()), - effective_gas_price: Some(tx_env.clone().gas_price.to_be_bytes().into()), /* TODO */ - transaction_hash: ethers::types::TxHash::from_slice(&hash), - to, - cumulative_gas_used: receipt_data - .cumulative_gas_per_block - .to_be_bytes() // TODO - .into(), - status: Some(1.into()), - root: None, - logs_bloom: { - let mut bloom = Bloom::default(); - for log in &logs { - bloom.accrue(BloomInput::Raw(&log.address.0)); - for topic in log.topics.iter() { - bloom.accrue(BloomInput::Raw(topic.as_bytes())); - } - } - bloom - }, - transaction_type: match tx { - TypedTransaction::Eip2930(_) => Some(1.into()), - _ => None, - }, - transaction_index: receipt_data.transaction_index, - ..Default::default() + match execution_result { + ExecutionResult::Revert { gas_used, output } => { + return Err(ArbiterCoreError::ExecutionRevert { + gas_used, + output: output.to_vec(), + }); + } + ExecutionResult::Halt { reason, gas_used } => { + return Err(ArbiterCoreError::ExecutionHalt { reason, gas_used }); + } + ExecutionResult::Success { + output, + gas_used, + logs, + .. + } => { + let logs = revm_logs_to_ethers_logs(logs); + let to: Option = match tx_env.transact_to { + TransactTo::Call(address) => Some(address.into_array().into()), + TransactTo::Create(_) => None, }; - // TODO: I'm not sure we need to set the confirmations. - let mut pending_tx = - PendingTransaction::new(ethers::types::H256::zero(), self.provider()) + // Note that this is technically not the correct construction on the tx hash + // but until we increment the nonce correctly this will do + let sender = self.address(); + let data = tx_env.clone().data; + let mut hasher = Sha256::new(); + hasher.update(sender.as_bytes()); + hasher.update(data.as_ref()); + let hash = hasher.finalize(); + + let mut block_hasher = Sha256::new(); + block_hasher.update(receipt_data.block_number.to_string().as_bytes()); + let block_hash = block_hasher.finalize(); + let block_hash = Some(H256::from_slice(&block_hash)); + + match output { + Output::Create(_, address) => { + let tx_receipt = TransactionReceipt { + block_hash, + block_number: Some(receipt_data.block_number), + contract_address: Some(recast_address(address.unwrap())), + logs: logs.clone(), + from: sender, + gas_used: Some(gas_used.into()), + effective_gas_price: Some( + tx_env.clone().gas_price.to_be_bytes().into(), + ), + transaction_hash: eTxHash::from_slice(&hash), + to, + cumulative_gas_used: receipt_data.cumulative_gas_per_block, + status: Some(1.into()), + root: None, + logs_bloom: { + let mut bloom = Bloom::default(); + for log in &logs { + bloom.accrue(BloomInput::Raw(&log.address.0)); + for topic in log.topics.iter() { + bloom.accrue(BloomInput::Raw(topic.as_bytes())); + } + } + bloom + }, + transaction_type: match tx { + TypedTransaction::Eip2930(_) => Some(1.into()), + _ => None, + }, + transaction_index: receipt_data.transaction_index, + ..Default::default() + }; + + // TODO: I'm not sure we need to set the confirmations. + let mut pending_tx = PendingTransaction::new( + ethers::types::H256::zero(), + self.provider(), + ) .interval(Duration::ZERO) .confirmations(0); - let state_ptr: *mut PendingTxState = - &mut pending_tx as *mut _ as *mut PendingTxState; + let state_ptr: *mut PendingTxState = + &mut pending_tx as *mut _ as *mut PendingTxState; - // Modify the value (this assumes you have access to the enum variants) - unsafe { - *state_ptr = PendingTxState::CheckingReceipt(Some(tx_receipt)); - } - - Ok(pending_tx) - } - Output::Call(_) => { - let tx_receipt = TransactionReceipt { - block_hash, - block_number: Some(receipt_data.block_number), - contract_address: None, - logs: logs.clone(), - from: sender, - gas_used: Some(gas_used.into()), - effective_gas_price: Some(tx_env.clone().gas_price.to_be_bytes().into()), - transaction_hash: ethers::types::TxHash::from_slice(&hash), - to, - cumulative_gas_used: receipt_data - .cumulative_gas_per_block - .to_be_bytes() - .into(), - status: Some(1.into()), - root: None, - logs_bloom: { - let mut bloom = Bloom::default(); - for log in &logs { - bloom.accrue(BloomInput::Raw(&log.address.0)); - for topic in log.topics.iter() { - bloom.accrue(BloomInput::Raw(topic.as_bytes())); - } + // Modify the value (this assumes you have access to the enum variants) + unsafe { + *state_ptr = PendingTxState::CheckingReceipt(Some(tx_receipt)); } - bloom - }, - transaction_type: match tx { - TypedTransaction::Eip2930(_) => Some(1.into()), - _ => None, - }, - transaction_index: receipt_data.transaction_index, - ..Default::default() - }; - // TODO: Create the actual tx_hash - // TODO: I'm not sure we need to set the confirmations. - let mut pending_tx = - PendingTransaction::new(ethers::types::H256::zero(), self.provider()) + Ok(pending_tx) + } + Output::Call(_) => { + let tx_receipt = TransactionReceipt { + block_hash, + block_number: Some(receipt_data.block_number), + contract_address: None, + logs: logs.clone(), + from: sender, + gas_used: Some(gas_used.into()), + effective_gas_price: Some( + tx_env.clone().gas_price.to_be_bytes().into(), + ), + transaction_hash: eTxHash::from_slice(&hash), + to, + cumulative_gas_used: receipt_data.cumulative_gas_per_block, + status: Some(1.into()), + root: None, + logs_bloom: { + let mut bloom = Bloom::default(); + for log in &logs { + bloom.accrue(BloomInput::Raw(&log.address.0)); + for topic in log.topics.iter() { + bloom.accrue(BloomInput::Raw(topic.as_bytes())); + } + } + bloom + }, + transaction_type: match tx { + TypedTransaction::Eip2930(_) => Some(1.into()), + _ => None, + }, + transaction_index: receipt_data.transaction_index, + ..Default::default() + }; + + // TODO: Create the actual tx_hash + // TODO: I'm not sure we need to set the confirmations. + let mut pending_tx = PendingTransaction::new( + ethers::types::H256::zero(), + self.provider(), + ) .interval(Duration::ZERO) .confirmations(0); - let state_ptr: *mut PendingTxState = - &mut pending_tx as *mut _ as *mut PendingTxState; + let state_ptr: *mut PendingTxState = + &mut pending_tx as *mut _ as *mut PendingTxState; - // Modify the value (this assumes you have access to the enum variants) - unsafe { - *state_ptr = PendingTxState::CheckingReceipt(Some(tx_receipt)); - } + // Modify the value (this assumes you have access to the enum variants) + unsafe { + *state_ptr = PendingTxState::CheckingReceipt(Some(tx_receipt)); + } - Ok(pending_tx) + Ok(pending_tx) + } + } } } } else { - panic!("This should never happen!") + unreachable!() } } @@ -688,7 +621,7 @@ impl Middleware for RevmMiddleware { &self, tx: &TypedTransaction, _block: Option, - ) -> Result { + ) -> Result { trace!("Building call"); let tx = tx.clone(); @@ -708,9 +641,7 @@ impl Middleware for RevmMiddleware { value: U256::ZERO, data: revm_primitives::Bytes(bytes::Bytes::from( tx.data() - .ok_or(RevmMiddlewareError::MissingData( - "Data missing in transaction!".to_string(), - ))? + .ok_or(ArbiterCoreError::MissingDataError)? .to_vec(), )), chain_id: None, @@ -723,29 +654,32 @@ impl Middleware for RevmMiddleware { tx_env, outcome_sender: self.provider().as_ref().outcome_sender.clone(), }; - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(instruction) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - } else { - return Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )); - } + self.provider() + .as_ref() + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(instruction)?; + let outcome = self.provider().as_ref().outcome_receiver.recv()??; if let Outcome::CallCompleted(execution_result) = outcome { - let output = unpack_execution_result(execution_result)?.output; - match output { - Output::Create(bytes, ..) => { - return Ok(Bytes::from(bytes.to_vec())); + match execution_result { + ExecutionResult::Revert { gas_used, output } => { + return Err(ArbiterCoreError::ExecutionRevert { + gas_used, + output: output.to_vec(), + }); } - Output::Call(bytes) => { - return Ok(Bytes::from(bytes.to_vec())); + ExecutionResult::Halt { reason, gas_used } => { + return Err(ArbiterCoreError::ExecutionHalt { reason, gas_used }); + } + ExecutionResult::Success { output, .. } => { + return Ok(eBytes::from(output.data().to_vec())); } } } else { - panic!("This should never happen!") + unreachable!() } } @@ -754,7 +688,8 @@ impl Middleware for RevmMiddleware { /// /// Currently, this method supports log filters. Other filters like /// `NewBlocks` and `PendingTransactions` are not yet implemented. - async fn new_filter(&self, filter: FilterKind<'_>) -> Result { + async fn new_filter(&self, filter: FilterKind<'_>) -> Result { + let provider = self.provider.as_ref(); let (_method, args) = match filter { FilterKind::NewBlocks => unimplemented!( "Filtering via new `FilterKind::NewBlocks` has not been implemented yet!" @@ -768,16 +703,15 @@ impl Middleware for RevmMiddleware { }; let filter = args.clone(); let mut hasher = Sha256::new(); - hasher.update(serde_json::to_string(&args).map_err(RevmMiddlewareError::Json)?); + hasher.update(serde_json::to_string(&args)?); let hash = hasher.finalize(); let id = ethers::types::U256::from(ethers::types::H256::from_slice(&hash).as_bytes()); - let event_receiver = self.provider().as_ref().event_sender.subscribe(); + let event_receiver = provider.event_sender.subscribe(); let filter_receiver = FilterReceiver { filter, receiver: Some(event_receiver), }; - self.provider() - .as_ref() + provider .filter_receivers .lock() .unwrap() @@ -793,56 +727,45 @@ impl Middleware for RevmMiddleware { async fn watch<'b>( &'b self, filter: &Filter, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let id = self.new_filter(FilterKind::Logs(filter)).await?; Ok(FilterWatcher::new(id, self.provider()).interval(Duration::ZERO)) } async fn get_gas_price(&self) -> Result { - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(Instruction::Query { - environment_data: EnvironmentData::GasPrice, - outcome_sender: self.provider().as_ref().outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - match self.provider().as_ref().outcome_receiver.recv()?? { - Outcome::QueryReturn(outcome) => { - ethers::types::U256::from_str_radix(outcome.as_ref(), 10) - .map_err(|e| RevmMiddlewareError::Conversion(e.to_string())) - } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via query!".to_string(), - )), + let provider = self.provider.as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::Query { + environment_data: EnvironmentData::GasPrice, + outcome_sender: provider.outcome_sender.clone(), + })?; + + match provider.outcome_receiver.recv()?? { + Outcome::QueryReturn(outcome) => { + Ok(ethers::types::U256::from_str_radix(outcome.as_ref(), 10)?) } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + _ => unreachable!(), } } async fn get_block_number(&self) -> Result { - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(Instruction::Query { - environment_data: EnvironmentData::BlockNumber, - outcome_sender: self.provider().as_ref().outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - match self.provider().as_ref().outcome_receiver.recv()?? { - Outcome::QueryReturn(outcome) => { - ethers::types::U64::from_str_radix(outcome.as_ref(), 10) - .map_err(|e| RevmMiddlewareError::Conversion(e.to_string())) - } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via query!".to_string(), - )), + let provider = self.provider().as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::Query { + environment_data: EnvironmentData::BlockNumber, + outcome_sender: provider.outcome_sender.clone(), + })?; + match provider.outcome_receiver.recv()?? { + Outcome::QueryReturn(outcome) => { + Ok(ethers::types::U64::from_str_radix(outcome.as_ref(), 10)?) } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + _ => unreachable!(), } } @@ -852,42 +775,29 @@ impl Middleware for RevmMiddleware { block: Option, ) -> Result { if block.is_some() { - return Err(RevmMiddlewareError::MissingData( - "Querying balance at a specific block is not supported!".to_string(), - )); + return Err(ArbiterCoreError::InvalidQueryError); } let address: NameOrAddress = from.into(); let address = match address { - NameOrAddress::Name(_) => { - return Err(RevmMiddlewareError::MissingData( - "Querying balance via name is not supported!".to_string(), - )) - } + NameOrAddress::Name(_) => return Err(ArbiterCoreError::InvalidQueryError), NameOrAddress::Address(address) => address, }; - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(Instruction::Query { - environment_data: EnvironmentData::Balance(ethers::types::Address::from( - address, - )), - outcome_sender: self.provider().as_ref().outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - match self.provider().as_ref().outcome_receiver.recv()?? { - Outcome::QueryReturn(outcome) => { - ethers::types::U256::from_str_radix(outcome.as_ref(), 10) - .map_err(|e| RevmMiddlewareError::Conversion(e.to_string())) - } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via query!".to_string(), - )), + let provider = self.provider.as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::Query { + environment_data: EnvironmentData::Balance(ethers::types::Address::from(address)), + outcome_sender: provider.outcome_sender.clone(), + })?; + + match provider.outcome_receiver.recv()?? { + Outcome::QueryReturn(outcome) => { + Ok(ethers::types::U256::from_str_radix(outcome.as_ref(), 10)?) } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + _ => unreachable!(), } } @@ -899,34 +809,24 @@ impl Middleware for RevmMiddleware { ) -> Result { let address: NameOrAddress = from.into(); let address = match address { - NameOrAddress::Name(_) => { - return Err(RevmMiddlewareError::MissingData( - "Querying storage via name is not supported!".to_string(), - )) - } + NameOrAddress::Name(_) => return Err(ArbiterCoreError::MissingDataError), NameOrAddress::Address(address) => address, }; - if let Some(instruction_sender) = self.provider().as_ref().instruction_sender.upgrade() { - instruction_sender - .send(Instruction::Query { - environment_data: EnvironmentData::TransactionCount(address), - outcome_sender: self.provider().as_ref().outcome_sender.clone(), - }) - .map_err(|e| RevmMiddlewareError::Send(e.to_string()))?; - - match self.provider().as_ref().outcome_receiver.recv()?? { - Outcome::QueryReturn(outcome) => { - ethers::types::U256::from_str_radix(outcome.as_ref(), 10) - .map_err(|e| RevmMiddlewareError::Conversion(e.to_string())) - } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via query!".to_string(), - )), + let provider = self.provider.as_ref(); + provider + .instruction_sender + .upgrade() + .ok_or(ArbiterCoreError::UpgradeSenderError)? + .send(Instruction::Query { + environment_data: EnvironmentData::TransactionCount(address), + outcome_sender: provider.outcome_sender.clone(), + })?; + + match provider.outcome_receiver.recv()?? { + Outcome::QueryReturn(outcome) => { + Ok(ethers::types::U256::from_str_radix(outcome.as_ref(), 10)?) } - } else { - Err(RevmMiddlewareError::Send( - "Environment is offline!".to_string(), - )) + _ => unreachable!(), } } @@ -963,14 +863,10 @@ impl Middleware for RevmMiddleware { account: T, key: ethers::types::H256, block: Option, - ) -> Result { + ) -> Result { let address: NameOrAddress = account.into(); let address = match address { - NameOrAddress::Name(_) => { - return Err(RevmMiddlewareError::MissingData( - "Querying storage via name is not supported!".to_string(), - )) - } + NameOrAddress::Name(_) => return Err(ArbiterCoreError::InvalidQueryError), NameOrAddress::Address(address) => address, }; @@ -990,24 +886,20 @@ impl Middleware for RevmMiddleware { let value: ethers::types::H256 = ethers::types::H256::from(value.to_be_bytes()); Ok(value) } - _ => Err(RevmMiddlewareError::MissingData( - "Wrong variant returned via cheatcode!".to_string(), - )), + _ => unreachable!(), } } async fn subscribe_logs<'a>( &'a self, filter: &Filter, - ) -> Result, Self::Error> + ) -> Result, Self::Error> where ::Provider: PubsubClient, { let watcher = self.watch(filter).await?; let id = watcher.id; - let subscription: Result, RevmMiddlewareError> = - SubscriptionStream::new(id, self.provider()).map_err(RevmMiddlewareError::Provider); - subscription + Ok(SubscriptionStream::new(id, self.provider())?) } async fn subscribe( @@ -1063,3 +955,14 @@ pub enum PendingTxState<'a> { /// Future has completed and should panic if polled again Completed, } + +// Certainly will go away with alloy-types +/// Recast a B160 into an Address type +/// # Arguments +/// * `address` - B160 to recast. (B160) +/// # Returns +/// * `Address` - Recasted Address. +#[inline] +pub fn recast_address(address: Address) -> eAddress { + eAddress::from(address.into_array()) +} diff --git a/arbiter-core/src/middleware/nonce_middleware.rs b/arbiter-core/src/middleware/nonce_middleware.rs index d06a14af6..bb58072db 100644 --- a/arbiter-core/src/middleware/nonce_middleware.rs +++ b/arbiter-core/src/middleware/nonce_middleware.rs @@ -9,11 +9,7 @@ //! - [`NonceManagerError`]: Error type for the middleware. use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use async_trait::async_trait; -use ethers::{ - providers::{Middleware, MiddlewareError, PendingTransaction}, - types::transaction::eip2718::TypedTransaction, -}; +use ethers::providers::MiddlewareError; use thiserror::Error; use super::*; @@ -26,7 +22,7 @@ pub struct NonceManagerMiddleware { init_guard: futures_locks::Mutex<()>, initialized: AtomicBool, nonce: AtomicU64, - address: Address, + address: eAddress, } impl NonceManagerMiddleware @@ -35,7 +31,7 @@ where { /// Instantiates the nonce manager with a 0 nonce. The `address` should be /// the address which you'll be sending transactions from - pub fn new(inner: M, address: Address) -> Self { + pub fn new(inner: M, address: eAddress) -> Self { Self { inner, init_guard: Default::default(), @@ -123,7 +119,7 @@ where /// Thrown when an error happens at the Nonce Manager pub enum NonceManagerError { /// Thrown when the internal middleware errors - #[error("{0}")] + #[error(transparent)] MiddlewareError(M::Error), } diff --git a/arbiter-core/src/middleware/transaction.rs b/arbiter-core/src/middleware/transaction.rs deleted file mode 100644 index b272940bc..000000000 --- a/arbiter-core/src/middleware/transaction.rs +++ /dev/null @@ -1,55 +0,0 @@ -use revm::primitives::{ExecutionResult, Output}; - -/// Unwraps the result of the EVM execution into a more structured `Success` -/// type. -use super::cast::revm_logs_to_ethers_logs; -use super::errors::RevmMiddlewareError; - -/// Contains the result of a successful transaction execution. -#[derive(Debug)] -pub struct Success { - /// The reason for the success. - pub _reason: revm::primitives::Eval, - /// The amount of gas used by the transaction. - pub _gas_used: u64, - /// The amount of gas refunded by the transaction. - pub _gas_refunded: u64, - /// The logs generated by the transaction. - pub logs: Vec, - /// The output of the transaction. - pub output: Output, -} - -/// Unpacks the result of the EVM execution. -/// -/// This function converts the raw execution result from the EVM into a more -/// structured [`Success`] type or an error indicating the failure of the -/// execution. -pub fn unpack_execution_result( - execution_result: ExecutionResult, -) -> Result { - match execution_result { - ExecutionResult::Success { - reason, - gas_used, - gas_refunded, - logs, - output, - } => { - let logs = revm_logs_to_ethers_logs(logs); - Ok(Success { - _reason: reason, - _gas_used: gas_used, - _gas_refunded: gas_refunded, - logs, - output, - }) - } - ExecutionResult::Revert { gas_used, output } => { - Err(RevmMiddlewareError::ExecutionRevert { gas_used, output }) - } - ExecutionResult::Halt { reason, gas_used } => { - Err(RevmMiddlewareError::ExecutionHalt { reason, gas_used }) - } - } -} diff --git a/arbiter-core/src/tests/mod.rs b/arbiter-core/tests/common.rs similarity index 64% rename from arbiter-core/src/tests/mod.rs rename to arbiter-core/tests/common.rs index daa42f7a0..e498aa478 100644 --- a/arbiter-core/src/tests/mod.rs +++ b/arbiter-core/tests/common.rs @@ -1,31 +1,11 @@ -#![allow(missing_docs)] - -mod contracts; -mod data_collection_integration; -mod environment_integration; -mod middleware_integration; - -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; use anyhow::Result; use arbiter_bindings::bindings::{ arbiter_math::ArbiterMath, arbiter_token::ArbiterToken, liquid_exchange::LiquidExchange, }; -use ethers::{ - prelude::{ - k256::sha2::{Digest, Sha256}, - EthLogDecode, Middleware, - }, - providers::ProviderError, - types::{Address, Filter, ValueOrArray, U256}, - utils::parse_ether, -}; -use futures::StreamExt; - -use crate::{ - environment::{cheatcodes::*, *}, - middleware::*, -}; +use arbiter_core::{environment::Environment, middleware::ArbiterMiddleware}; +use ethers::utils::parse_ether; pub const TEST_ARG_NAME: &str = "ArbiterToken"; pub const TEST_ARG_SYMBOL: &str = "ARBT"; @@ -48,13 +28,13 @@ pub const ARBITER_TOKEN_Y_DECIMALS: u8 = 18; pub const LIQUID_EXCHANGE_PRICE: f64 = 420.69; -fn startup() -> Result<(Environment, Arc)> { +fn startup() -> Result<(Environment, Arc)> { let env = Environment::builder().build(); - let client = RevmMiddleware::new(&env, Some(TEST_SIGNER_SEED_AND_LABEL))?; + let client = ArbiterMiddleware::new(&env, Some(TEST_SIGNER_SEED_AND_LABEL))?; Ok((env, client)) } -async fn deploy_arbx(client: Arc) -> Result> { +async fn deploy_arbx(client: Arc) -> Result> { Ok(ArbiterToken::deploy( client, ( @@ -67,7 +47,7 @@ async fn deploy_arbx(client: Arc) -> Result) -> Result> { +async fn deploy_arby(client: Arc) -> Result> { Ok(ArbiterToken::deploy( client, ( @@ -81,11 +61,11 @@ async fn deploy_arby(client: Arc) -> Result, + client: Arc, ) -> Result<( - ArbiterToken, - ArbiterToken, - LiquidExchange, + ArbiterToken, + ArbiterToken, + LiquidExchange, )> { let arbx = deploy_arbx(client.clone()).await?; let arby = deploy_arby(client.clone()).await?; @@ -96,6 +76,8 @@ async fn deploy_liquid_exchange( Ok((arbx, arby, liquid_exchange)) } -async fn deploy_arbiter_math(client: Arc) -> Result> { +async fn deploy_arbiter_math( + client: Arc, +) -> Result> { Ok(ArbiterMath::deploy(client, ())?.send().await?) } diff --git a/arbiter-core/src/tests/contracts.rs b/arbiter-core/tests/contracts.rs similarity index 98% rename from arbiter-core/src/tests/contracts.rs rename to arbiter-core/tests/contracts.rs index d845a52e8..f9ed6d290 100644 --- a/arbiter-core/src/tests/contracts.rs +++ b/arbiter-core/tests/contracts.rs @@ -1,8 +1,8 @@ use std::fs::{self, File}; +use ethers::prelude::Middleware; use tracing_subscriber::{fmt, EnvFilter}; - -use super::*; +include!("common.rs"); #[tokio::test] async fn arbiter_math() { @@ -314,17 +314,14 @@ async fn price_simulation_oracle() { async fn can_log() { std::env::set_var("RUST_LOG", "trace"); let file = File::create("test_logs.log").expect("Unable to create log file"); - let subscriber = fmt() .with_env_filter(EnvFilter::from_default_env()) .with_writer(file) .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - // tracing_subscriber::fmt::init(); let env = Environment::builder().with_console_logs().build(); - let client = RevmMiddleware::new(&env, None).unwrap(); + let client = ArbiterMiddleware::new(&env, None).unwrap(); let counter = arbiter_bindings::bindings::counter::Counter::deploy(client, ()) .unwrap() .send() diff --git a/arbiter-core/src/tests/data_collection_integration.rs b/arbiter-core/tests/data_collection_integration.rs similarity index 86% rename from arbiter-core/src/tests/data_collection_integration.rs rename to arbiter-core/tests/data_collection_integration.rs index 71311cc6c..353eb142a 100644 --- a/arbiter-core/src/tests/data_collection_integration.rs +++ b/arbiter-core/tests/data_collection_integration.rs @@ -1,12 +1,13 @@ use std::path::Path; -use serde::Serialize; - -use super::*; -use crate::{ +use arbiter_core::{ data_collection::{EventLogger, OutputFileType}, - middleware::errors::RevmMiddlewareError, + errors::ArbiterCoreError, }; +use ethers::types::U256 as eU256; +use futures::StreamExt; +use serde::Serialize; +include!("common.rs"); #[derive(Serialize, Clone)] struct MockMetadata { @@ -14,23 +15,23 @@ struct MockMetadata { } async fn generate_events( - arbx: ArbiterToken, - arby: ArbiterToken, - lex: LiquidExchange, - client: Arc, -) -> Result<(), RevmMiddlewareError> { + arbx: ArbiterToken, + arby: ArbiterToken, + lex: LiquidExchange, + client: Arc, +) -> Result<(), ArbiterCoreError> { for _ in 0..2 { - arbx.approve(client.address(), U256::from(1)) + arbx.approve(client.address(), eU256::from(1)) .send() .await .unwrap() .await?; - arby.approve(client.address(), U256::from(1)) + arby.approve(client.address(), eU256::from(1)) .send() .await .unwrap() .await?; - lex.set_price(U256::from(10u128.pow(18))) + lex.set_price(eU256::from(10u128.pow(18))) .send() .await .unwrap() diff --git a/arbiter-core/src/tests/environment_integration.rs b/arbiter-core/tests/environment_integration.rs similarity index 86% rename from arbiter-core/src/tests/environment_integration.rs rename to arbiter-core/tests/environment_integration.rs index 8542bf7cf..4e30951c2 100644 --- a/arbiter-core/src/tests/environment_integration.rs +++ b/arbiter-core/tests/environment_integration.rs @@ -1,7 +1,15 @@ -use arbiter_bindings::bindings::{self, weth::weth}; +use std::str::FromStr; -use super::*; -use crate::environment::fork::Fork; +use arbiter_bindings::bindings::{self, weth::weth}; +use arbiter_core::database::fork::Fork; +use ethers::{ + prelude::{ + k256::sha2::{Digest, Sha256}, + Middleware, + }, + types::{Address, U256 as eU256}, +}; +include!("common.rs"); #[tokio::test] async fn receipt_data() { @@ -33,7 +41,7 @@ async fn receipt_data() { assert_eq!(receipt.transaction_index, 1.into()); assert_eq!(receipt.from, client.default_sender().unwrap()); - let mut cumulative_gas = U256::from(0); + let mut cumulative_gas = eU256::from(0); assert!(receipt.cumulative_gas_used >= cumulative_gas); cumulative_gas += receipt.cumulative_gas_used; @@ -88,7 +96,7 @@ async fn fork_into_arbiter() { let environment = Environment::builder().with_db(fork.db).build(); // Create a client - let client = RevmMiddleware::new(&environment, Some("name")).unwrap(); + let client = ArbiterMiddleware::new(&environment, Some("name")).unwrap(); // Deal with the weth contract let weth_meta = fork.contracts_meta.get("weth").unwrap(); @@ -103,13 +111,13 @@ async fn fork_into_arbiter() { .call() .await .unwrap(); - assert_eq!(balance, U256::from(34890707020710109111_u128)); + assert_eq!(balance, eU256::from(34890707020710109111_u128)); // eoa check let eoa = fork.eoa.get("vitalik").unwrap(); let eth_balance = client.get_balance(*eoa, None).await.unwrap(); // Check the balance of the eoa with the load cheatcode - assert_eq!(eth_balance, U256::from(934034962177715175765_u128)); + assert_eq!(eth_balance, eU256::from(934034962177715175765_u128)); } #[tokio::test] @@ -120,7 +128,8 @@ async fn middleware_from_forked_eo() { let environment = Environment::builder().with_db(fork.db).build(); let vitalik_address = fork.eoa.get("vitalik").unwrap(); - let vitalik_as_a_client = RevmMiddleware::new_from_forked_eoa(&environment, *vitalik_address); + let vitalik_as_a_client = + ArbiterMiddleware::new_from_forked_eoa(&environment, *vitalik_address); assert!(vitalik_as_a_client.is_ok()); let vitalik_as_a_client = vitalik_as_a_client.unwrap(); @@ -136,7 +145,7 @@ async fn middleware_from_forked_eo() { .get_balance(*vitalik_address, None) .await .unwrap(); - assert_eq!(eth_balance, U256::from(934034962177715175765_u128)); + assert_eq!(eth_balance, eU256::from(934034962177715175765_u128)); } #[tokio::test] @@ -144,6 +153,5 @@ async fn env_returns_db() { let (environment, client) = startup().unwrap(); deploy_arbx(client).await.unwrap(); let db = environment.stop().unwrap(); - assert!(db.is_some()); - assert!(!db.unwrap().0.read().unwrap().accounts.is_empty()) + assert!(!db.0.read().unwrap().accounts.is_empty()) } diff --git a/arbiter-core/src/tests/middleware_integration.rs b/arbiter-core/tests/middleware_integration.rs similarity index 92% rename from arbiter-core/src/tests/middleware_integration.rs rename to arbiter-core/tests/middleware_integration.rs index 4f4b893ec..b5e8f683c 100644 --- a/arbiter-core/src/tests/middleware_integration.rs +++ b/arbiter-core/tests/middleware_integration.rs @@ -1,11 +1,17 @@ +use std::str::FromStr; + use arbiter_bindings::bindings::arbiter_token::ApprovalFilter; +use arbiter_core::{ + environment::instruction::{Cheatcodes, CheatcodesReturn}, + middleware::nonce_middleware::NonceManagerMiddleware, +}; use ethers::{ - types::{transaction::eip2718::TypedTransaction, Log}, - utils::parse_ether, + prelude::{EthLogDecode, Middleware}, + providers::ProviderError, + types::{transaction::eip2718::TypedTransaction, Address, Filter, Log, ValueOrArray}, }; - -use super::*; -use crate::middleware::nonce_middleware::NonceManagerMiddleware; +use futures::StreamExt; +include!("common.rs"); #[tokio::test] async fn deploy() { @@ -104,7 +110,11 @@ async fn filter_watcher() { .unwrap() ); let approval_filter_output = ApprovalFilter::decode_log(&event.into()).unwrap(); - println!("Decoded Log: {:#?}", approval_filter_output); + println!( + "Decoded +Log: {:#?}", + approval_filter_output + ); assert_eq!( approval_filter_output.owner, client.default_sender().unwrap() @@ -142,7 +152,7 @@ async fn filter_address() { assert!(!address_watcher_event.data.is_empty()); assert_eq!(default_watcher_event, address_watcher_event); - // Create a new token contract to check that the address watcher only gets + // Create a new token contract to check that the address watcher onlygets // events from the correct contract. // Check that only the default watcher gets // this event @@ -175,9 +185,9 @@ async fn filter_address() { // check that the address_watcher has not received any events tokio::select! { - _ = address_watcher.next() => panic!("Event received unexpectedly!"), - _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => println!("No event captured, as expected. This test passes."), - }; + _ = address_watcher.next() => panic!("Event received unexpectedly!"), + _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => + println!("No event captured, as expected. This test passes."), }; } #[tokio::test] @@ -214,9 +224,10 @@ async fn filter_topics() { // check that the approval_watcher has not received any events tokio::select! { - _ = approval_watcher.next() => panic!("Event received unexpectedly!"), - _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => println!("No event captured, as expected. This test passes."), - }; + _ = approval_watcher.next() => panic!("Event received + unexpectedly!"), _ = + tokio::time::sleep(std::time::Duration::from_secs(1)) => println!("No event + captured, as expected. This test passes."), }; } #[tokio::test] @@ -239,7 +250,7 @@ async fn block_update_receipt() { assert_eq!(receipt.block_number, 0.into()); assert_eq!( receipt.cumulative_gas_per_block, - revm::primitives::U256::from_str_radix("e12e4", 16).unwrap() + ethers::types::U256::from_str_radix("e12e4", 16).unwrap() ); assert_eq!(receipt.transaction_index, 2.into()); } @@ -456,13 +467,11 @@ async fn unimplemented_middleware_instruction() { let (_environment, client) = startup().unwrap(); // This method is not implemented and likely never will, so it works to test - // what happens when we send an unimplemented instruction. We should get a + // what happens when we send an unimplemented instruction. We shouldget a // "this method is not yet implemented" error. let should_be_error = client.client_version().await; assert!(should_be_error.is_err()); - if let crate::middleware::errors::RevmMiddlewareError::Provider(e) = - should_be_error.unwrap_err() - { + if let arbiter_core::errors::ArbiterCoreError::ProviderError(e) = should_be_error.unwrap_err() { assert_eq!( e.to_string(), ProviderError::CustomError(format!( @@ -517,16 +526,16 @@ fn simulation_signer() -> Result<()> { #[test] fn multiple_signer_addresses() { let environment = Environment::builder().build(); - let client_1 = RevmMiddleware::new(&environment, Some("0")).unwrap(); - let client_2 = RevmMiddleware::new(&environment, Some("1")).unwrap(); + let client_1 = ArbiterMiddleware::new(&environment, Some("0")).unwrap(); + let client_2 = ArbiterMiddleware::new(&environment, Some("1")).unwrap(); assert_ne!(client_1.address(), client_2.address()); } #[test] fn signer_collision() { let environment = Environment::builder().build(); - RevmMiddleware::new(&environment, Some("0")).unwrap(); - assert!(RevmMiddleware::new(&environment, Some("0")).is_err()); + ArbiterMiddleware::new(&environment, Some("0")).unwrap(); + assert!(ArbiterMiddleware::new(&environment, Some("0")).is_err()); } #[tokio::test] @@ -564,7 +573,6 @@ async fn access() { .unwrap() .await .unwrap(); - let bal_after = arbiter_token.balance_of(to).call().await.unwrap(); assert_eq!(bal_after, amount); diff --git a/arbiter-engine/src/agent.rs b/arbiter-engine/src/agent.rs index 9f5fd59b9..ff64bd639 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -1,7 +1,15 @@ //! The agent module contains the core agent abstraction for the Arbiter Engine. -use super::*; -use crate::machine::{Behavior, Engine, StateMachine}; +use std::{fmt::Debug, sync::Arc}; + +use arbiter_core::middleware::ArbiterMiddleware; +use serde::{de::DeserializeOwned, Serialize}; +use thiserror::Error; + +use crate::{ + machine::{Behavior, Engine, StateMachine}, + messager::Messager, +}; /// An agent is an entity capable of processing events and producing actions. /// These are the core actors in simulations or in onchain systems. @@ -24,7 +32,7 @@ pub struct Agent { pub messager: Messager, /// The client the agent uses to interact with the blockchain. - pub client: Arc, + pub client: Arc, /// The engines/behaviors that the agent uses to sync, startup, and process /// events. @@ -145,7 +153,7 @@ impl AgentBuilder { /// ``` pub fn build( self, - client: Arc, + client: Arc, messager: Messager, ) -> Result { match self.behavior_engines { diff --git a/arbiter-engine/src/examples/minter/agents/token_admin.rs b/arbiter-engine/src/examples/minter/agents/token_admin.rs index 6507accf5..5979b3752 100644 --- a/arbiter-engine/src/examples/minter/agents/token_admin.rs +++ b/arbiter-engine/src/examples/minter/agents/token_admin.rs @@ -5,9 +5,9 @@ pub(crate) struct TokenAdmin { /// The identifier of the token admin. pub token_data: HashMap, #[serde(skip)] - pub tokens: Option>>, + pub tokens: Option>>, #[serde(skip)] - pub client: Option>, + pub client: Option>, #[serde(skip)] pub messager: Option, #[serde(default)] diff --git a/arbiter-engine/src/examples/minter/agents/token_requester.rs b/arbiter-engine/src/examples/minter/agents/token_requester.rs index 5a3229e30..41af65f8c 100644 --- a/arbiter-engine/src/examples/minter/agents/token_requester.rs +++ b/arbiter-engine/src/examples/minter/agents/token_requester.rs @@ -10,7 +10,7 @@ pub(crate) struct TokenRequester { pub request_to: String, /// Client to have an address to receive token mint to and check balance #[serde(skip)] - pub client: Option>, + pub client: Option>, /// The messaging layer for the token requester. #[serde(skip)] pub messager: Option, diff --git a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs index f4d968988..0eea2a0b8 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_admin.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs @@ -29,7 +29,7 @@ impl Behavior for TokenAdmin { #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] async fn startup( &mut self, - client: Arc, + client: Arc, messager: Messager, ) -> Result, ArbiterEngineError> { self.messager = Some(messager.clone()); diff --git a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs index 1616905b8..58274ed37 100644 --- a/arbiter-engine/src/examples/minter/behaviors/token_requester.rs +++ b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs @@ -13,7 +13,7 @@ impl Behavior for TokenRequester { #[tracing::instrument(skip(self), fields(id = messager.id.as_deref()))] async fn startup( &mut self, - client: Arc, + client: Arc, mut messager: Messager, ) -> Result, ArbiterEngineError> { messager diff --git a/arbiter-engine/src/examples/minter/mod.rs b/arbiter-engine/src/examples/minter/mod.rs index b6bf9e336..23515012b 100644 --- a/arbiter-engine/src/examples/minter/mod.rs +++ b/arbiter-engine/src/examples/minter/mod.rs @@ -36,7 +36,7 @@ pub(crate) struct TokenData { #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn token_minter_simulation() { let mut world = World::new("test_world"); - let client = RevmMiddleware::new(&world.environment, None).unwrap(); + let client = ArbiterMiddleware::new(&world.environment, None).unwrap(); // Create the token admin agent let token_admin = Agent::builder(TOKEN_ADMIN_ID); diff --git a/arbiter-engine/src/examples/mod.rs b/arbiter-engine/src/examples/mod.rs index 63147553c..ad22bf1af 100644 --- a/arbiter-engine/src/examples/mod.rs +++ b/arbiter-engine/src/examples/mod.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::Arc}; use arbiter_bindings::bindings::arbiter_token::ArbiterToken; -use arbiter_core::middleware::RevmMiddleware; +use arbiter_core::middleware::ArbiterMiddleware; use ethers::types::{transaction::eip2718::TypedTransaction, Address, Log, U256}; use futures_util::{stream, StreamExt}; diff --git a/arbiter-engine/src/examples/timed_message/mod.rs b/arbiter-engine/src/examples/timed_message/mod.rs index ed1cb324f..6413b324c 100644 --- a/arbiter-engine/src/examples/timed_message/mod.rs +++ b/arbiter-engine/src/examples/timed_message/mod.rs @@ -54,7 +54,7 @@ impl TimedMessage { impl Behavior for TimedMessage { async fn startup( &mut self, - _client: Arc, + _client: Arc, messager: Messager, ) -> Result, ArbiterEngineError> { if let Some(startup_message) = &self.startup_message { diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 5c76ddcf2..746fabca6 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -3,6 +3,7 @@ use std::pin::Pin; +use arbiter_core::middleware::ArbiterMiddleware; use futures_util::{Stream, StreamExt}; use tokio::task::JoinHandle; @@ -23,7 +24,7 @@ pub type EventStream = Pin + Send + Sync>>; #[derive(Clone, Debug)] pub enum MachineInstruction { /// Used to make a [`StateMachine`] start up. - Start(Arc, Messager), + Start(Arc, Messager), /// Used to make a [`StateMachine`] process events. /// This will offload the process into a task that can be halted by sending @@ -74,9 +75,9 @@ pub trait Behavior: Serialize + DeserializeOwned + Send + Sync + Debug + 'sta /// that it can do given the current state of the world. async fn startup( &mut self, - client: Arc, + client: Arc, messager: Messager, - ) -> Result, ArbiterEngineError>; + ) -> EventStream; /// Used to process events. /// This is where the agent can engage in its specific processing diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index 9dca5211f..b46146175 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -2,7 +2,10 @@ use std::collections::VecDeque; -use arbiter_core::environment::Environment; +use arbiter_core::{environment::Environment, middleware::ArbiterMiddleware}; +use futures_util::future::join_all; +use serde::de::DeserializeOwned; +use tokio::spawn; use super::*; use crate::{ @@ -147,7 +150,7 @@ impl World { /// This will add the agent defined by `agent_builder` to the world. pub fn add_agent(&mut self, agent_builder: AgentBuilder) { let id = agent_builder.id.clone(); - let client = RevmMiddleware::new(&self.environment, Some(&id)) + let client = ArbiterMiddleware::new(&self.environment, Some(&id)) .expect("Failed to create RevmMiddleware client for agent"); let messager = self.messager.for_agent(&id); let agent = agent_builder diff --git a/bin/fork/mod.rs b/bin/fork/mod.rs index 9b9f51b1d..6282abf4e 100644 --- a/bin/fork/mod.rs +++ b/bin/fork/mod.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, io::Write, sync::Arc}; -use arbiter_core::environment::fork::*; +use arbiter_core::database::fork::*; use ethers::{ providers::{Http, Provider}, types::{Address, BlockId, BlockNumber, U256}, diff --git a/bin/fork/tests.rs b/bin/fork/tests.rs index 76779ff4e..05ae5bad9 100644 --- a/bin/fork/tests.rs +++ b/bin/fork/tests.rs @@ -1,4 +1,4 @@ -use arbiter_core::environment::fork::Fork; +use arbiter_core::database::fork::Fork; use super::*; diff --git a/documentation/src/usage/arbiter_core/middleware.md b/documentation/src/usage/arbiter_core/middleware.md index 6ff029340..8c9209cd7 100644 --- a/documentation/src/usage/arbiter_core/middleware.md +++ b/documentation/src/usage/arbiter_core/middleware.md @@ -1,46 +1,44 @@ # Middleware -The `RevmMiddleware` is the main interface for interacting with an `Environment`. +The `ArbiterMiddleware` is the main interface for interacting with an `Environment`. We implement the `ethers-rs` `Middleware` trait so that you may work with contract bindings generated by `forge` or `arbiter bind` as if you were interacting with a live network. Not all methods are implemented, but the relevant ones are. -`RevmMiddleware` owns a `Connection` which is the client's interface to the `Environment`'s `Socket`. +`ArbiterMiddleware` owns a `Connection` which is the client's interface to the `Environment`'s `Socket`. This `Connection` acts much like a WebSocket connection and is used to send `Instruction`s and receive their outcome from the `Environment` as well as subscribe to events. -To make this `Connection` and `RevmMiddleware` flexible, we also implement (for both) the `JsonRpcClient` and `PubSubClient` traits. +To make this `Connection` and `ArbiterMiddleware` flexible, we also implement (for both) the `JsonRpcClient` and `PubSubClient` traits. -We also provide `RevmMiddleware` a wallet so that it can be associated to an account in the `Environment`'s world state. -The `wallet: EOA` field of `RevmMiddleware` is decided upon creation of the `RevmMiddleware` and, if the wallet is generated from calling `RevmMiddleware::new()`, wallet will be of `EOA::Wallet(Wallet)` which allows for `RevmMiddleware` to sign transactions if need be. -It is possible to create accounts from a forked database, in which case you would call `RevmMiddleware::new_from_forked_eoa()` and the wallet would be of `EOA::Forked(Address)`. +We also provide `ArbiterMiddleware` a wallet so that it can be associated to an account in the `Environment`'s world state. +The `wallet: EOA` field of `ArbiterMiddleware` is decided upon creation of the `ArbiterMiddleware` and, if the wallet is generated from calling `ArbiterMiddleware::new()`, wallet will be of `EOA::Wallet(Wallet)` which allows for `ArbiterMiddleware` to sign transactions if need be. +It is possible to create accounts from a forked database, in which case you would call `ArbiterMiddleware::new_from_forked_eoa()` and the wallet would be of `EOA::Forked(Address)`. This type is unable to sign as it is effectively impossible to recover the signing key from an address. -Fortunately, for almost every usecase of `RevmMiddleware`, you will not need to sign transactions, so this distinction does not matter. +Fortunately, for almost every usecase of `ArbiterMiddleware`, you will not need to sign transactions, so this distinction does not matter. ## Usage -To create a `RevmMiddleware` that is associated with an account in the `Environment`'s world state, we can do the following: +To create a `ArbiterMiddleware` that is associated with an account in the `Environment`'s world state, we can do the following: ```rust -use arbiter_core::middleware::RevmMiddleware; -use arbiter_core::environment::Environment; +use arbiter_core::{middleware::ArbiterMiddleware, environment::Environment}; fn main() { let env = Environment::builder().build(); // Create a client for the above `Environment` with an ID let id = "alice"; - let alice = RevmMiddleware::new(&env, Some(id)); + let alice = ArbiterMiddleware::new(&env, Some(id)); // Create a client without an ID - let client = RevmMiddleware::new(&env, None); + let client = ArbiterMiddleware::new(&env, None); } ``` These created clients can then get access to making calls and transactions to contracts deployed into the `Environment`'s world state. We can do the following: ```rust -use arbiter_core::middleware::RevmMiddleware; -use arbiter_core::environment::Environment; +use arbiter_core::{middleware::ArbiterMiddleware, environment::Environment}; use arbiter_bindings::bindings::arbiter_token::ArbiterToken; #[tokio::main] async fn main() { let env = Environment::builder().build(); - let client = RevmMiddleware::new(&env, None).unwrap(); + let client = ArbiterMiddleware::new(&env, None).unwrap(); // Deploy a contract let contract = ArbiterToken::deploy(client, ("ARBT".to_owned(), "Arbiter Token".to_owned(), 18u8)).unwrap().send().await.unwrap(); From fad2e20feea4b0d8066ab8f7227fc535b14b38bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:06:06 -0700 Subject: [PATCH 35/38] build(deps): bump async-trait from 0.1.76 to 0.1.77 (#849) Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.76 to 0.1.77. - [Release notes](https://github.com/dtolnay/async-trait/releases) - [Commits](https://github.com/dtolnay/async-trait/commits) --- updated-dependencies: - dependency-name: async-trait dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f4647d04..f88bfd84a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -530,9 +530,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.76" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 583772dc0..6b432a241 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ proc-macro2 = { version = "1.0.78" } tokio = { version = "1.36.0", features = ["macros", "full"] } crossbeam-channel = { version = "0.5.11" } futures-util = { version = "0.3.30" } -async-trait = { version = "0.1.76" } +async-trait = { version = "0.1.77" } tracing = "0.1.40" async-stream = "0.3.5" toml = "0.8.10" From 0b90212eb6aea9654bf45e70fbe95683caf592cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:06:18 -0700 Subject: [PATCH 36/38] build(deps): bump thiserror from 1.0.55 to 1.0.56 (#850) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.55 to 1.0.56. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.55...1.0.56) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f88bfd84a..ba1f679b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5229,18 +5229,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.55" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.55" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 6b432a241..e29bc95a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ revm-primitives = "=2.0.0" ethers = { version = "2.0.13" } serde = { version = "1.0.193", features = ["derive"] } serde_json = { version = "1.0.113" } -thiserror = { version = "1.0.55" } +thiserror = { version = "1.0.56" } syn = { version = "2.0.48", features = ["full"] } proc-macro2 = { version = "1.0.78" } tokio = { version = "1.36.0", features = ["macros", "full"] } From ca09b29818f10f17c04a95ce33f56a01faaa5bd3 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen <57912727+0xJepsen@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:14:00 -0700 Subject: [PATCH 37/38] chore: remove codecov (#860) * remove codecov chore: remove codecov chore: pull out badge * chore: remove codecov in test.yaml * chore: code spell --- .github/codecov.yml | 17 ----------------- .github/workflows/test.yaml | 24 ------------------------ README.md | 1 - documentation/src/usage/index.md | 2 +- 4 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml deleted file mode 100644 index f3070d457..000000000 --- a/.github/codecov.yml +++ /dev/null @@ -1,17 +0,0 @@ -coverage: - status: - project: - default: - target: 50% - threshold: 1% - patch: - default: - target: 50% - threshold: 1% - informational: true - ignore: - - "arbiter-core/contracts/*" - - "arbiter/arbiter-core/benches*" - - "arbiter/arbiter-core/src/bindings" - - "arbiter/arbiter-bindings/*" - diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 12237c821..34e1674c6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,27 +21,3 @@ jobs: - name: test run: cargo test --workspace --all-features --exclude documentation - - codecov: - name: codecov - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: install rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - - name: install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - - name: codecov - run: cargo llvm-cov --all-features --workspace --exclude documentation --lcov --output-path lcov.info - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - with: - files: lcov.info - fail_ci_if_error: true diff --git a/README.md b/README.md index bf5fd70f6..362a11445 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ > Expanding the EVM tooling ecosystem. ![Github Actions](https://github.com/primitivefinance/arbiter/workflows/test/badge.svg) -[![Codecov badge](https://codecov.io/gh/primitivefinance/arbiter/branch/main/graph/badge.svg?token=UQ1SE0D9IN)](https://codecov.io/gh/primitivefinance/arbiter) ![Visitors badge](https://visitor-badge.laobi.icu/badge?page_id=arbiter) ![Telegram badge](https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Farbiter_rs) [![Twitter Badge](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/primitivefi) diff --git a/documentation/src/usage/index.md b/documentation/src/usage/index.md index d789c6dd7..23ac7b5ec 100644 --- a/documentation/src/usage/index.md +++ b/documentation/src/usage/index.md @@ -5,7 +5,7 @@ Arbiter is broken into a number of crates that provide different levels of abstr The `arbiter-core` crate is the core of the Arbiter. It contains the `Environment` struct which acts as an EVM sandbox and the `RevmMiddleware` which gives a convenient interface for interacting with contracts deployed into the `Environment`. Direct usage of `arbiter-core` will be minimized as much as possible as it is intended for developers to mostly pull from the `arbiter-engine` crate in the future. -This crate provides the interface for agents to interact with an in memeory evm. +This crate provides the interface for agents to interact with an in memory evm. ## Arbiter Engine The `arbiter-engine` crate is the main interface for running simulations. From 9548f619e401b9391d2756d3db30d16e5abdc0ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:18:17 -0700 Subject: [PATCH 38/38] build(deps): bump tempfile from 3.9.0 to 3.10.0 (#844) Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.9.0 to 3.10.0. - [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md) - [Commits](https://github.com/Stebalien/tempfile/compare/v3.9.0...v3.10.0) --- updated-dependencies: - dependency-name: tempfile dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba1f679b7..87787fb3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5178,13 +5178,12 @@ checksum = "cfb5fa503293557c5158bd215fdc225695e567a77e453f5d4452a50a193969bd" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] diff --git a/Cargo.toml b/Cargo.toml index e29bc95a0..b45600f50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ Inflector = { version = "=0.11.4" } # Building files foundry-config = { version = "=0.2.0" } -tempfile = { version = "3.9.0" } +tempfile = { version = "3.10.0" } # Errors thiserror.workspace = true