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/.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/Cargo.lock b/Cargo.lock index aecde45f6..0bfb432d9 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" @@ -111,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", @@ -122,6 +111,7 @@ dependencies = [ "derive_more", "hex-literal", "itoa", + "k256", "keccak-asm", "proptest", "rand", @@ -149,7 +139,7 @@ checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -183,9 +173,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" @@ -242,17 +232,16 @@ dependencies = [ "ethers", "foundry-config", "proc-macro2", - "quote", "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.43", + "syn 2.0.48", "tempfile", "thiserror", "tokio", - "toml 0.8.8", + "toml 0.8.10", ] [[package]] @@ -287,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", @@ -297,15 +286,16 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-test", + "uint", ] [[package]] name = "arbiter-engine" version = "0.1.0" dependencies = [ - "anyhow", "arbiter-bindings", "arbiter-core", + "arbiter-macros", "async-stream", "async-trait", "crossbeam-channel", @@ -314,13 +304,23 @@ dependencies = [ "futures-util", "serde", "serde_json", + "thiserror", "tokio", "tokio-stream", + "toml 0.8.10", "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,18 +515,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[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", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -576,14 +576,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.48", ] [[package]] @@ -637,29 +636,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.43", - "which", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -762,7 +738,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", ] @@ -792,9 +768,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", ] @@ -807,7 +783,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -848,11 +824,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", @@ -916,15 +891,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" @@ -977,17 +943,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" @@ -1019,7 +974,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1100,11 +1055,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", @@ -1113,7 +1069,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml 0.5.11", + "toml 0.8.10", "yaml-rust", ] @@ -1136,6 +1092,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" @@ -1148,6 +1124,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" @@ -1326,7 +1311,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", @@ -1410,9 +1395,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" @@ -1526,7 +1514,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1537,7 +1525,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1646,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", @@ -1662,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", @@ -1674,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", @@ -1693,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", @@ -1710,16 +1698,16 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.43", - "toml 0.8.8", + "syn 2.0.48", + "toml 0.8.10", "walkdir", ] [[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", @@ -1728,14 +1716,14 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.43", + "syn 2.0.48", ] [[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", @@ -1754,7 +1742,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.43", + "syn 2.0.48", "tempfile", "thiserror", "tiny-keccak", @@ -1763,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", @@ -1779,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", @@ -1806,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", @@ -1844,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", @@ -1863,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", @@ -1904,9 +1892,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", @@ -1960,7 +1948,7 @@ dependencies = [ "atomic", "pear", "serde", - "toml 0.8.8", + "toml 0.8.10", "uncased", "version_check", ] @@ -2043,7 +2031,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", @@ -2138,7 +2126,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -2235,7 +2223,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -2291,22 +2279,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]] @@ -2315,7 +2294,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", @@ -2526,9 +2505,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", @@ -2718,12 +2697,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" @@ -2790,19 +2763,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" - -[[package]] -name = "libloading" -version = "0.8.1" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -3074,6 +3037,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" @@ -3145,7 +3114,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3202,12 +3171,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]] @@ -3355,15 +3324,9 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.43", + "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" @@ -3410,7 +3373,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3484,7 +3447,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3507,22 +3470,22 @@ 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", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -3644,7 +3607,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "befd4d280a82219a01035c4f901319ceba65998c594d0c64f9a439cdee1d7777" dependencies = [ - "ahash 0.8.7", + "ahash", "bitflags 2.4.2", "bytemuck", "chrono", @@ -3688,7 +3651,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51fba2cf014cb39c2b38353d601540fb9db643be65abb9ca8ff44b9c4c4a88e" dependencies = [ - "ahash 0.8.7", + "ahash", "async-trait", "atoi_simd", "bytes", @@ -3726,7 +3689,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "973d1f40ba964e70cf0038779056a7850f649538f72d8828c21bc1a7bce312ed" dependencies = [ - "ahash 0.8.7", + "ahash", "chrono", "fallible-streaming-iterator", "hashbrown 0.14.3", @@ -3747,7 +3710,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d83343e413346f048f3a5ad07c0ea4b5d0bada701a482878213142970b0ddff8" dependencies = [ - "ahash 0.8.7", + "ahash", "bitflags 2.4.2", "glob", "once_cell", @@ -3771,7 +3734,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6395f5fd5e1adf016fd6403c0a493181c1a349a7a145b2687cdf50a0d630310a" dependencies = [ - "ahash 0.8.7", + "ahash", "argminmax", "base64 0.21.7", "bytemuck", @@ -3801,7 +3764,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b664cac41636cc9f146fba584a8e7c2790d7335a278964529fa3e9b4eae96daf" dependencies = [ - "ahash 0.8.7", + "ahash", "async-stream", "base64 0.21.7", "brotli", @@ -3851,7 +3814,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb7d7527be2aa33baace9000f6772eb9df7cd57ec010a4b273435d2dc1349e8" dependencies = [ - "ahash 0.8.7", + "ahash", "bytemuck", "chrono-tz", "once_cell", @@ -3926,7 +3889,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f9c955bb1e9b55d835aeb7fe4e4e8826e01abe5f0ada979ceb7d2b9af7b569" dependencies = [ - "ahash 0.8.7", + "ahash", "bytemuck", "hashbrown 0.14.3", "indexmap", @@ -3991,7 +3954,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]] @@ -4033,31 +3996,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.21.0", -] - -[[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", + "toml_edit 0.21.1", ] [[package]] @@ -4077,7 +4016,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "version_check", "yansi 1.0.0-rc.1", ] @@ -4104,11 +4043,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", ] @@ -4121,9 +4060,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", ] @@ -4246,18 +4185,18 @@ checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[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", ] @@ -4272,9 +4211,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", @@ -4301,9 +4240,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", @@ -4327,6 +4266,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -4341,10 +4281,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", @@ -4357,23 +4299,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", @@ -4398,15 +4343,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,13 +4434,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]] @@ -4528,9 +4476,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", @@ -4542,12 +4490,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" @@ -4574,9 +4516,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", @@ -4735,9 +4677,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", ] @@ -4798,29 +4740,29 @@ 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", - "syn 2.0.43", + "syn 2.0.48", ] [[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", @@ -4910,12 +4852,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4954,7 +4890,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", @@ -5170,7 +5106,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5194,9 +5130,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", @@ -5238,15 +5174,21 @@ 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", "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" @@ -5296,13 +5238,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", ] @@ -5342,27 +5283,27 @@ checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[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", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5386,12 +5327,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", @@ -5406,10 +5348,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", ] @@ -5439,9 +5382,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", @@ -5464,7 +5407,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5517,15 +5460,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" @@ -5541,14 +5475,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.22.4", ] [[package]] @@ -5586,9 +5520,20 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +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 = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" dependencies = [ "indexmap", "serde", @@ -5622,7 +5567,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -5755,9 +5700,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", ] @@ -5800,8 +5745,6 @@ checksum = "0bea5dacebb0d2d0a69a6700a05b59b3908bf801bf563a49bd27a1b60122962c" dependencies = [ "unicode-segmentation", ] - -[[package]] name = "unicode-segmentation" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5872,9 +5815,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,7 +5886,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -5977,7 +5920,7 @@ checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6004,23 +5947,11 @@ 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.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68938b57b33da363195412cfc5fc37c9ed49aa9cfe2156fde64b8d2c9498242" +checksum = "89beec544f246e679fc25490e3f8e08003bc4bf612068f325120dad4cea02c1c" dependencies = [ "bytemuck", "safe_arch", @@ -6210,9 +6141,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", ] @@ -6299,7 +6230,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -6319,7 +6250,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 ec37abee8..b45600f50 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,23 @@ path = "bin/main.rs" [workspace.dependencies] arbiter-bindings = { version = "*", path = "./arbiter-bindings" } arbiter-core = { version = "*", path = "./arbiter-core" } -ethers = { version = "2.0.11" } +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" } -thiserror = { version = "1.0.55" } -syn = { version = "2.0.43" } -quote = { version = "=1.0.33" } +serde_json = { version = "1.0.113" } +thiserror = { version = "1.0.56" } +syn = { version = "2.0.48", features = ["full"] } proc-macro2 = { version = "1.0.78" } -tokio = { version = "1.35.1", features = ["macros", "full"] } -crossbeam-channel = { version = "0.5.11" } -futures-util = { version = "=0.3.30" } -async-trait = { version = "0.1.76" } +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.77" } tracing = "0.1.40" async-stream = "0.3.5" +toml = "0.8.10" # Dependencies for the release build [dependencies] @@ -47,18 +58,17 @@ 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.8" } +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.10.0" } # Errors thiserror.workspace = true @@ -76,5 +86,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/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/arbiter-core/Cargo.toml b/arbiter-core/Cargo.toml index cea0d1927..5aa8731c8 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.37.0", 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.37.0" 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 e47b6d09e..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)> { - let environment = EnvironmentBuilder::new().build(); +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 72c09bf96..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() } } @@ -49,14 +38,13 @@ mod tests { use revm_primitives::{InvalidTransaction, U256}; use super::*; - use crate::environment::EnvironmentBuilder; #[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(); + 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 e659b3b9a..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,13 +59,24 @@ pub(crate) type FilterDecoder = pub struct EventLogger { decoder: FilterDecoder, receiver: Option>, - // shutdown_sender: Option>, output_file_type: Option, directory: Option, file_name: Option, 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)] @@ -122,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(); @@ -150,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) @@ -246,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 91% rename from arbiter-core/src/database.rs rename to arbiter-core/src/database/mod.rs index 423e6f428..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`. @@ -227,7 +228,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-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 46734624b..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; +use crate::middleware::ArbiterMiddleware; +use crate::{console::abi::HardhatConsoleCalls, database::inspector::ArbiterInspector}; -pub mod cheatcodes; -use cheatcodes::*; - -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,25 +114,10 @@ pub struct EnvironmentParameters { /// size limit, and a database for the [`Environment`]. pub struct EnvironmentBuilder { parameters: EnvironmentParameters, - db: Option, -} - -impl Default for EnvironmentBuilder { - fn default() -> Self { - Self::new() - } + db: ArbiterDB, } 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 { @@ -219,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 } @@ -239,7 +165,16 @@ impl EnvironmentBuilder { } impl Environment { - fn create(parameters: EnvironmentParameters, db: Option) -> Self { + /// Creates a new [`EnvironmentBuilder`] with default parameters that can be + /// used to build an [`Environment`]. + pub fn builder() -> EnvironmentBuilder { + EnvironmentBuilder { + parameters: EnvironmentParameters::default(), + db: ArbiterDB(Arc::new(RwLock::new(CacheDB::new(EmptyDB::new())))), + } + } + + fn create(parameters: EnvironmentParameters, db: ArbiterDB) -> Self { let (instruction_sender, instruction_receiver) = unbounded(); let (event_broadcaster, _) = channel(512); let socket = Socket { @@ -266,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(); @@ -293,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() { @@ -309,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() @@ -324,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))?; } } } @@ -345,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, @@ -370,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))?; } }; } @@ -418,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 } }; @@ -505,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))?; } } } @@ -530,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. @@ -565,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, @@ -616,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); } @@ -631,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) { @@ -689,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; } } @@ -702,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 { @@ -739,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) } } @@ -784,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 0f3419071..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 = EnvironmentBuilder::new() - .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 9c83c1078..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::EnvironmentBuilder, middleware::RevmMiddleware}; +/// use arbiter_core::{environment::Environment, middleware::ArbiterMiddleware}; /// /// // 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")); +/// 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::EnvironmentBuilder, middleware::RevmMiddleware}; + /// use arbiter_core::{environment::Environment, middleware::ArbiterMiddleware}; /// /// // 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")); + /// 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 62% rename from arbiter-core/src/tests/mod.rs rename to arbiter-core/tests/common.rs index 53b767b1f..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)> { - let env = EnvironmentBuilder::new().build(); - let client = RevmMiddleware::new(&env, Some(TEST_SIGNER_SEED_AND_LABEL))?; +fn startup() -> Result<(Environment, Arc)> { + let env = Environment::builder().build(); + 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 bec7097dc..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 = EnvironmentBuilder::new().with_console_logs().build(); - let client = RevmMiddleware::new(&env, None).unwrap(); + let env = Environment::builder().with_console_logs().build(); + 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 83% rename from arbiter-core/src/tests/environment_integration.rs rename to arbiter-core/tests/environment_integration.rs index 5a63f9d0f..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; @@ -85,10 +93,10 @@ 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(); + 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] @@ -117,10 +125,11 @@ 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); + 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 45030febe..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!( @@ -516,17 +525,17 @@ fn simulation_signer() -> Result<()> { #[test] fn multiple_signer_addresses() { - let environment = EnvironmentBuilder::new().build(); - let client_1 = RevmMiddleware::new(&environment, Some("0")).unwrap(); - let client_2 = RevmMiddleware::new(&environment, Some("1")).unwrap(); + let environment = Environment::builder().build(); + 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 = EnvironmentBuilder::new().build(); - RevmMiddleware::new(&environment, Some("0")).unwrap(); - assert!(RevmMiddleware::new(&environment, Some("0")).is_err()); + let environment = Environment::builder().build(); + 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/Cargo.toml b/arbiter-engine/Cargo.toml index fef73219b..ebb80572b 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,13 +23,14 @@ 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" +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 e862e9e00..ff64bd639 100644 --- a/arbiter-engine/src/agent.rs +++ b/arbiter-engine/src/agent.rs @@ -1,138 +1,88 @@ -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// 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, 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 arbiter_core::middleware::ArbiterMiddleware; +use serde::{de::DeserializeOwned, Serialize}; +use thiserror::Error; -use self::machine::MachineInstruction; -use super::*; use crate::{ - machine::{Behavior, Engine, State, StateMachine}, + machine::{Behavior, Engine, StateMachine}, messager::Messager, - world::World, }; -// TODO: For the time being, these agents are just meant to be for arbiter -// instances. We can generalize later. - /// An agent is an entity capable of processing events and producing actions. /// These are the core actors in simulations or in onchain systems. /// Agents can be connected of other agents either as a dependent, or a /// 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. +#[derive(Debug)] pub struct Agent { /// Identifier for this 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: Option, + pub messager: Messager, /// 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, + pub client: Arc, /// 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), - - broadcast_task: Option + Send>>>>, + pub(crate) behavior_engines: Vec>, } 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(); - let distributor = channel(512); - Self { + /// 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(), - state: State::Uninitialized, - messager: Some(messager), - client, - event_streamer: Some(EventLogger::builder()), behavior_engines: None, - distributor, - broadcast_task: 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 - } +/// [`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. + pub id: String, + /// The engines/behaviors that the agent uses to sync, startup, and process + /// events. + behavior_engines: Option>>, +} - /// Adds a behavior to the agent that it will run. - pub fn with_behavior( +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, + behavior: impl Behavior + Serialize + DeserializeOwned + '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 { @@ -141,176 +91,81 @@ 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::>(), - ); + /// 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(crate) 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 } -} - -#[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::Sync(_, _) => { - debug!("Agent is syncing."); - self.state = State::Syncing; - self.run(MachineInstruction::Sync( - self.messager.clone(), - Some(self.client.clone()), - )) - .await; - } - MachineInstruction::Start => { - debug!("Agent is starting up."); - self.run(instruction).await; - } - 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; - } - MachineInstruction::Stop => { - unreachable!("This is never explicitly called on an agent.") - } + /// 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, + messager: Messager, + ) -> Result { + match self.behavior_engines { + Some(engines) => Ok(Agent { + id: self.id, + messager, + client, + behavior_engines: engines, + }), + None => Err(ArbiterEngineError::AgentBuildError( + "Missing behavior engines".to_owned(), + )), } } } - -#[cfg(test)] -mod tests { - use arbiter_bindings::bindings::arbiter_token::ArbiterToken; - use ethers::types::U256; - - use super::*; - use crate::messager::Message; - - #[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::new("agent", &world); - - 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; - } -} 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/agents/mod.rs b/arbiter-engine/src/examples/minter/agents/mod.rs new file mode 100644 index 000000000..7311e5619 --- /dev/null +++ b/arbiter-engine/src/examples/minter/agents/mod.rs @@ -0,0 +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 new file mode 100644 index 000000000..5979b3752 --- /dev/null +++ b/arbiter-engine/src/examples/minter/agents/token_admin.rs @@ -0,0 +1,35 @@ +use super::*; + +#[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, +} + +impl TokenAdmin { + pub fn new(max_count: Option) -> Self { + Self { + token_data: HashMap::new(), + tokens: None, + client: None, + messager: None, + count: 0, + 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); + } +} 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..41af65f8c --- /dev/null +++ b/arbiter-engine/src/examples/minter/agents/token_requester.rs @@ -0,0 +1,39 @@ +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, 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, +} + +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..2998593ee --- /dev/null +++ b/arbiter-engine/src/examples/minter/behaviors/mod.rs @@ -0,0 +1,3 @@ +use super::*; +pub(crate) mod token_admin; +pub(crate) 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..0eea2a0b8 --- /dev/null +++ b/arbiter-engine/src/examples/minter/behaviors/token_admin.rs @@ -0,0 +1,107 @@ +use self::{examples::minter::agents::token_admin::TokenAdmin, machine::EventStream}; +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, + ) -> Result, ArbiterEngineError> { + 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()); + } + messager.stream() + } + + #[tracing::instrument(skip(self), fields(id = + self.messager.as_ref().unwrap().id.as_deref()))] + 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 + 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(); + messager + .send(To::Agent(event.from.clone()), token_data.address) + .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 Ok(ControlFlow::Halt); + } + } + } + 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 new file mode 100644 index 000000000..58274ed37 --- /dev/null +++ b/arbiter-engine/src/examples/minter/behaviors/token_requester.rs @@ -0,0 +1,68 @@ +use arbiter_bindings::bindings::arbiter_token::TransferFilter; +use arbiter_core::data_collection::EventLogger; +use token_admin::{MintRequest, TokenAdminQuery}; + +use self::{ + errors::ArbiterEngineError, examples::minter::agents::token_requester::TokenRequester, + machine::EventStream, +}; +use super::*; + +#[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, + ) -> Result, ArbiterEngineError> { + messager + .send( + To::Agent(self.request_to.clone()), + &TokenAdminQuery::AddressOf(self.token_data.name.clone()), + ) + .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); + + let mint_data = TokenAdminQuery::MintRequest(MintRequest { + token: self.token_data.name.clone(), + mint_to: client.address(), + mint_amount: 1, + }); + messager + .send(To::Agent(self.request_to.clone()), mint_data) + .await; + + self.messager = Some(messager.clone()); + self.client = Some(client.clone()); + 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) -> Result { + let messager = self.messager.as_ref().unwrap(); + while (self.count < self.max_count.unwrap()) { + debug!("sending message from requester"); + 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; + } + Ok(ControlFlow::Halt) + } +} 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 new file mode 100644 index 000000000..23515012b --- /dev/null +++ b/arbiter-engine/src/examples/minter/mod.rs @@ -0,0 +1,99 @@ +use super::*; +pub(crate) mod agents; +pub(crate) mod behaviors; +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, ControlFlow, 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; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct TokenData { + pub name: String, + pub symbol: String, + 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 = ArbiterMiddleware::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 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/mod.rs b/arbiter-engine/src/examples/mod.rs index 5ca2c05e4..ad22bf1af 100644 --- a/arbiter-engine/src/examples/mod.rs +++ b/arbiter-engine/src/examples/mod.rs @@ -1,22 +1,24 @@ #![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. - 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}; use super::*; -use crate::messager::{Message, Messager}; -mod timed_message; -mod token_minter; +use crate::{ + agent::Agent, + errors::ArbiterEngineError, + machine::{ + Behavior, ControlFlow, CreateStateMachine, Engine, EventStream, 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..8ef7e5ddd --- /dev/null +++ b/arbiter-engine/src/examples/timed_message/config.toml @@ -0,0 +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.rs b/arbiter-engine/src/examples/timed_message/mod.rs similarity index 50% rename from arbiter-engine/src/examples/timed_message.rs rename to arbiter-engine/src/examples/timed_message/mod.rs index df76c0c84..6413b324c 100644 --- a/arbiter-engine/src/examples/timed_message.rs +++ b/arbiter-engine/src/examples/timed_message/mod.rs @@ -1,27 +1,33 @@ -#[cfg(test)] +use super::*; const AGENT_ID: &str = "agent"; -use std::time::Duration; +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, -}; - -struct TimedMessage { + +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, } impl TimedMessage { @@ -30,6 +36,7 @@ impl TimedMessage { receive_data: String, send_data: String, max_count: Option, + startup_message: Option, ) -> Self { Self { delay, @@ -38,46 +45,35 @@ impl TimedMessage { messager: None, count: 0, max_count, + startup_message, } } } #[async_trait::async_trait] impl Behavior for TimedMessage { - 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; + async fn startup( + &mut self, + _client: Arc, + messager: Messager, + ) -> Result, ArbiterEngineError> { + if let Some(startup_message) = &self.startup_message { + messager.send(To::All, startup_message).await; + } + self.messager = Some(messager.clone()); + messager.stream() + } + + 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) { - warn!("Reached max count. Halting behavior."); - return Some(MachineHalt); + return Ok(ControlFlow::Halt); } - - tokio::time::sleep(std::time::Duration::from_secs(self.delay)).await; - trace!("Processed event."); - None - } - - async fn sync(&mut self, messager: Messager, _client: Arc) { - trace!("Syncing state for `TimedMessage`."); - self.messager = Some(messager); - 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`."); + Ok(ControlFlow::Continue) } } @@ -85,27 +81,20 @@ 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); let behavior = TimedMessage::new( 1, "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.for_agent("outside_world"); - let messager = world.messager.join_with_id(Some("god".to_owned())); - let task = world.run(); + world.run().await; - 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 stream = messager.stream().unwrap(); let mut idx = 0; loop { @@ -128,28 +117,26 @@ async fn echoer() { 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 agent = Agent::builder(AGENT_ID); + 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())); - 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; + let messager = world.messager.for_agent("outside_world"); + world.run().await; - task.await; - - let mut stream = Box::pin(messager.stream()); + let mut stream = messager.stream().unwrap(); let mut idx = 0; loop { @@ -172,29 +159,25 @@ 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_ping = Agent::builder("agent_ping"); + let agent_pong = Agent::builder("agent_pong"); - let agent_pong = Agent::new("agent_pong", &world); - 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), + 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())); - let task = world.run(); - - let init_message = Message { - from: "god".to_owned(), - to: To::All, - data: "ping".to_owned(), - }; - - messager.send(init_message).await; + let messager = world.messager.for_agent("outside_world"); + world.run().await; - task.await; - - let mut stream = Box::pin(messager.stream()); + let mut stream = messager.stream().unwrap(); let mut idx = 0; loop { @@ -212,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/examples/token_minter.rs b/arbiter-engine/src/examples/token_minter.rs deleted file mode 100644 index 230bacae7..000000000 --- a/arbiter-engine/src/examples/token_minter.rs +++ /dev/null @@ -1,378 +0,0 @@ -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( - client: Arc, - messager: Messager, - 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) - } -} - -#[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 - } -} - -#[ignore] -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn token_minter_simulation() { - let mut world = World::new("test_world"); - - // Create the token admin agent - let token_admin = Agent::new(TOKEN_ADMIN_ID, &world); - 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::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 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), - ); - 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/lib.rs b/arbiter-engine/src/lib.rs index 1a2869242..b304a573b 100644 --- a/arbiter-engine/src/lib.rs +++ b/arbiter-engine/src/lib.rs @@ -5,14 +5,20 @@ //! 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 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; +pub mod universe; pub mod world; diff --git a/arbiter-engine/src/machine.rs b/arbiter-engine/src/machine.rs index 856f42c97..746fabca6 100644 --- a/arbiter-engine/src/machine.rs +++ b/arbiter-engine/src/machine.rs @@ -1,49 +1,48 @@ //! The [`StateMachine`] trait, [`Behavior`] trait, and the [`Engine`] that runs //! [`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. +use std::pin::Pin; -// 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 arbiter_core::middleware::ArbiterMiddleware; +use futures_util::{Stream, StreamExt}; +use tokio::task::JoinHandle; -use std::{fmt::Debug, sync::Arc}; - -use arbiter_core::middleware::RevmMiddleware; -use serde::de::DeserializeOwned; -use tokio::sync::broadcast::Receiver; - -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 { - /// Used to make a [`StateMachine`] sync with the world. - Sync(Option, Option>), - /// Used to make a [`StateMachine`] start up. - Start, + Start(Arc, Messager), /// 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, - - /// 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. -/// 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)] @@ -52,12 +51,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 +61,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 @@ -80,64 +69,136 @@ 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 { - /// 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) {} - +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) {} + 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; + async fn process(&mut self, event: E) -> Result; +} +/// 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 + 'static { - async fn execute(&mut self, instruction: MachineInstruction); +/// 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) -> Result<(), ArbiterEngineError>; } -/// 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. -pub struct Engine +/// 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(crate) struct Engine where B: Behavior, { - /// The behavior the [`Engine`] runs. - pub behavior: Option, + /// The behavior the `Engine` runs. + 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 /// into the event type ``. - event_receiver: Option>, + event_stream: Option>, 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, event_receiver: Receiver) -> Self { + pub fn new(behavior: B) -> Self { Self { behavior: Some(behavior), state: State::Uninitialized, - event_receiver: Some(event_receiver), + event_stream: None, phantom: std::marker::PhantomData, } } @@ -146,65 +207,49 @@ where #[async_trait::async_trait] impl StateMachine for Engine where - B: Behavior, - 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) { + 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::Sync(messager, client) => { - trace!("Behavior is syncing."); - self.state = State::Syncing; - 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."); + MachineInstruction::Start(client, messager) => { self.state = State::Starting; let mut behavior = self.behavior.take().unwrap(); - let behavior_task = tokio::spawn(async move { - behavior.startup().await; - behavior - }); - self.behavior = Some(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); + self.execute(MachineInstruction::Process).await?; + Ok(()) } MachineInstruction::Process => { - trace!("Behavior is processing."); + trace!("Behavior is starting up."); let mut behavior = self.behavior.take().unwrap(); - let mut receiver = self.event_receiver.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() { + let mut stream = self.event_stream.take().unwrap(); + 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 => {} } - 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; - } - }, } - } - behavior - }); - self.behavior = Some(behavior_task.await.unwrap()); - } - MachineInstruction::Stop => { - unreachable!("This is never explicitly called on an engine.") + 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 041b7ccd6..6d95b4a00 100644 --- a/arbiter-engine/src/messager.rs +++ b/arbiter-engine/src/messager.rs @@ -1,9 +1,9 @@ //! The messager module contains the core messager layer for the Arbiter Engine. -use futures_util::Stream; use tokio::sync::broadcast::{channel, Receiver, Sender}; use super::*; +use crate::machine::EventStream; /// A message that can be sent between agents. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -51,9 +51,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 +62,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,11 +72,49 @@ impl Messager { } } + /// utility function for getting the next value from the broadcast_receiver + /// without streaming + 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 Ok(message); + } + To::Agent(id) => { + if let Some(self_id) = &self.id { + if id == self_id { + return Ok(message); + } + } + continue; + } + } + } + unreachable!() + } + /// 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 { - let mut receiver = self.broadcast_receiver.take().unwrap(); - 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 => { @@ -94,22 +129,41 @@ 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) { + /// 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) -> Result<(), ArbiterEngineError> { trace!("Sending message via messager."); - 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 new file mode 100644 index 000000000..0a9671837 --- /dev/null +++ b/arbiter-engine/src/universe.rs @@ -0,0 +1,161 @@ +//! The [`universe`] module contains the [`Universe`] struct which is the +//! primary interface for creating and running many `World`s in parallel. + +use super::*; +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 { + /// Creates a new [`Universe`]. + pub fn new() -> Self { + Self { + worlds: Some(HashMap::new()), + world_tasks: None, + } + } + + /// 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<(), ArbiterEngineError> { + if self.is_online() { + return Err(ArbiterEngineError::UniverseError( + "Universe is already running.".to_owned(), + )); + } + let mut tasks = Vec::new(); + // 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(); + world + })); + } + self.world_tasks = Some(join_all(tasks.into_iter()).await); + Ok(()) + } + + /// Returns `true` if the [`Universe`] is running. + pub fn is_online(&self) -> bool { + 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::*}; + + #[tokio::test] + async fn run_universe() { + let mut universe = Universe::new(); + let world = World::new("test"); + universe.add_world(world); + universe.run_worlds().await.unwrap(); + universe.world_tasks.unwrap().remove(0).unwrap(); + } + + #[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::builder("agent1"); + 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::builder("agent2"); + 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 = "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", + ); + } + + 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; + } + } + } + } + + false + } +} diff --git a/arbiter-engine/src/world.rs b/arbiter-engine/src/world.rs index e435e1658..b46146175 100644 --- a/arbiter-engine/src/world.rs +++ b/arbiter-engine/src/world.rs @@ -1,31 +1,16 @@ -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// 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 arbiter_core::environment::{Environment, EnvironmentBuilder}; +use std::collections::VecDeque; + +use arbiter_core::{environment::Environment, middleware::ArbiterMiddleware}; use futures_util::future::join_all; -use tokio::sync::broadcast::Sender as BroadcastSender; -use tracing::info; +use serde::de::DeserializeOwned; +use tokio::spawn; -use self::machine::{MachineHalt, MachineInstruction}; use super::*; use crate::{ - agent::Agent, - machine::{State, StateMachine}, - messager::Messager, + agent::{Agent, AgentBuilder}, + machine::{CreateStateMachine, MachineInstruction}, }; /// A world is a collection of agents that use the same type of provider, e.g., @@ -33,39 +18,19 @@ use crate::{ /// 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`]). +#[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>, - agent_distributors: Option>>, - /// The environment for the world. pub environment: Environment, @@ -73,173 +38,178 @@ pub struct World { pub messager: Messager, } +use std::{fs::File, io::Read}; 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()), - agent_distributors: None, - environment: EnvironmentBuilder::new().build(), + environment: Environment::builder().build(), messager: Messager::new(), } } - /// 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, - agent_distributors: None, - environment, - messager: Messager::new(), + /// 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, + ) -> Result<(), ArbiterEngineError> { + let cwd = std::env::current_dir()?; + let path = cwd.join(config_path); + let mut file = File::open(path)?; + + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + 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 to the world. - pub fn add_agent(&mut self, agent: Agent) { - let id = agent.id.clone(); - let agents = self.agents.as_mut().unwrap(); + /// 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 = 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 + .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 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::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 -// 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::Sync(_, _) => { - 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::>(), - ); + /// 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<(), 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(), + )) } - 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| { - 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::>(), - ); + }; + let mut tasks = vec![]; + // 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()); } - MachineInstruction::Process => { - 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 + } + // 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(); + let messager = messagers.pop_front().unwrap(); + tasks.push(spawn(async move { + engine + .execute(MachineInstruction::Start(client, messager)) .await - .into_iter() - .map(|res| { - let agent = res.unwrap(); - (agent.id.clone(), agent) - }) - .collect::>(), - ); - } - MachineInstruction::Stop => { - let halt = serde_json::to_string(&MachineHalt).unwrap(); - for tx in self.agent_distributors.take().unwrap() { - tx.send(halt.clone()).unwrap(); - } + })); } } - } -} - -#[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); + // 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) +} 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/SUMMARY.md b/documentation/src/SUMMARY.md index 81d2ed47c..d0ec6eebe 100644 --- a/documentation/src/SUMMARY.md +++ b/documentation/src/SUMMARY.md @@ -1,18 +1,19 @@ # 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) - - [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 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 5c9b6f475..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.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_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..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::EnvironmentBuilder; +use arbiter_core::{middleware::ArbiterMiddleware, 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"; - 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::EnvironmentBuilder; +use arbiter_core::{middleware::ArbiterMiddleware, environment::Environment}; use arbiter_bindings::bindings::arbiter_token::ArbiterToken; #[tokio::main] async fn main() { - let env = EnvironmentBuilder::new().build(); - let client = RevmMiddleware::new(&env, None).unwrap(); + let env = Environment::builder().build(); + 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(); 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..e477c8ecb --- /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 +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. + +## `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, ignore +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, ignore +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..ff2c0ce9b --- /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, ignore +pub trait Behavior { + fn startup(&mut self, client: Arc, messager: Messager) -> Result, ArbiterEngineError>; + fn process(&mut self, event: E) -> Result; +} +``` +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 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. + +**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, ignore +use std::sync::Arc; +use arbiter_core::middleware::RevmMiddleware; +use arbiter_engine::{ + machine::{Behavior, ControlFlow}, + 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, + ) -> Result, ArbiterEngineError> { + if let Some(startup_message) = &self.startup_message { + messager.send(To::All, startup_message).await; + } + self.messager = Some(messager.clone()); + messager.stream() + } + + 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 Ok(ControlFlow::Halt); + } + Ok(ControlFlow::Continue) + } +} +``` +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..19b5873de --- /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, ignore +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, ignore +#[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, ignore +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..3f8167018 --- /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. + +## 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`. + - `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..1fd8f25ad --- /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, ignore +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, ignore +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, ignore +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); +} + +async fn run() { + 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, ignore +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; +} +``` 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 diff --git a/documentation/src/usage/index.md b/documentation/src/usage/index.md index 0803351fc..23ac7b5ec 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 memory 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