diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82a1307cb9..d04e934abe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,11 +15,9 @@ jobs: runs-on: ubuntu-latest-4-cores steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - uses: arduino/setup-protoc@v1 with: @@ -31,17 +29,16 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} targets: wasm32-unknown-unknown - - uses: Swatinem/rust-cache@v2 - - name: "Ensure `torii-client` crate is wasmable" + - uses: arduino/setup-protoc@v2 + - name: "Ensure `torii-client` crate is WASM-able" run: | cargo build -r --target wasm32-unknown-unknown -p torii-client - - name: "Ensure `torii-client-wasm` crate is wasmable" + - name: "Ensure `torii-client-wasm` crate is WASM-able" run: | cargo build -r --target wasm32-unknown-unknown --manifest-path crates/torii/client/wasm/Cargo.toml @@ -57,29 +54,48 @@ jobs: # - run: cargo install cairo-lang-formatter # - run: scripts/cairo_fmt.sh --check - cairotest: + dojo-core-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + - uses: Swatinem/rust-cache@v2 + - uses: arduino/setup-protoc@v2 + - run: cargo run --bin sozo -- --manifest-path crates/dojo-core/Scarb.toml test + + dojo-erc-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + - uses: Swatinem/rust-cache@v2 + - uses: arduino/setup-protoc@v2 + - run: cargo run --bin sozo -- --manifest-path crates/dojo-erc/Scarb.toml test + + dojo-ecs-example-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - - run: scripts/cairo_test.sh + - uses: arduino/setup-protoc@v2 + - run: cargo run --bin sozo -- --manifest-path examples/ecs/Scarb.toml test clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} targets: wasm32-unknown-unknown components: clippy - - uses: Swatinem/rust-cache@v2 - uses: arduino/setup-protoc@v1 with: @@ -90,19 +106,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master with: toolchain: nightly components: rustfmt - - uses: Swatinem/rust-cache@v2 - uses: arduino/setup-protoc@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - run: scripts/rust_fmt.sh --check - - name: "Format TOML files" - run: cargo install taplo-cli && taplo format # Check for unnecessary dependencies. # udeps: @@ -138,11 +150,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - uses: arduino/setup-protoc@v1 with: @@ -158,11 +168,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_VERSION }} - - uses: Swatinem/rust-cache@v2 - name: Install Hurl run: | diff --git a/.gitignore b/.gitignore index 384b889cc8..1e38a84066 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target +**/target workspace .idea/ dojo.iml diff --git a/.vscode/launch.json b/.vscode/launch.json index 305c8be121..c4e9c9fd50 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,61 +7,21 @@ { "type": "lldb", "request": "launch", - "name": "Debug unit tests in library 'dojo-world'", + "name": "Debug unit tests in 'dojo-world'", "cargo": { "args": [ "test", "--no-run", - "--lib", - "--package=dojo-world" + "--package=dojo-world", + "--lib" ], "filter": { "name": "dojo-world", "kind": "lib" } }, - "args": [], - "env": { - "CARGO_MANIFEST_DIR": "${workspaceFolder}/crates/dojo-world" - }, + "args": ["migration::compile_moves"], "cwd": "${workspaceFolder}/crates/dojo-world" }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'dojo-compile'", - "cargo": { - "args": [ - "build", - "--bin=dojo-compile", - "--package=dojo-lang" - ], - "filter": { - "name": "dojo-compile", - "kind": "bin" - } - }, - "args": ["${workspaceFolder}/crates/dojo-lang/src/cairo_level_tests/component.cairo"], - "cwd": "${workspaceFolder}/crates/dojo-lang" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'dojo-compile'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=dojo-compile", - "--package=dojo-lang" - ], - "filter": { - "name": "dojo-compile", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}/crates/dojo-lang" - } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 80ba90fe4e..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cairo1.languageServerPath": "~/.dojo/bin/dojo-language-server", - "cairo1.enableLanguageServer": true, - "cairo1.enableScarb": false, - "rust-analyzer.linkedProjects": [ - "./crates/dojo-lang/Cargo.toml" - ], - "rust-analyzer.rustfmt.extraArgs": [ - "+nightly" - ] -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5797bd58e8..e3c3b429c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] @@ -108,24 +108,23 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" @@ -147,9 +146,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -157,9 +156,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arc-swap" @@ -337,22 +336,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - [[package]] name = "async-compression" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b74f44609f0f91493e3082d3734d98497e094777144380ea4db9f9905dd5b6" +checksum = "bb42b2197bf15ccb092b62c74515dbd8b86d0effd934795f6687c93b6e679a2c" dependencies = [ "brotli", "flate2", @@ -426,19 +414,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "async-graphql-poem" -version = "5.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f818938d4e47dcc40bc383e9ddec373e9aab1db29e5ad9706b29621afe3b3f" -dependencies = [ - "async-graphql", - "futures-util", - "poem", - "serde_json", - "tokio-util", -] - [[package]] name = "async-graphql-value" version = "5.0.10" @@ -452,32 +427,15 @@ dependencies = [ ] [[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-std" -version = "1.12.0" +name = "async-graphql-warp" +version = "5.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "ce971f92675defe1adf14f9e70b8798d797db9f454463b611a552bffd5532188" dependencies = [ - "async-channel", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", + "async-graphql", + "futures-util", + "serde_json", + "warp", ] [[package]] @@ -499,7 +457,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -510,7 +468,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -587,9 +545,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -608,9 +566,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "beef" @@ -710,7 +668,7 @@ dependencies = [ [[package]] name = "blockifier" version = "0.1.0-rc0" -source = "git+https://github.com/dojoengine/blockifier?rev=c794d1b#c794d1b85b0937de9e0f4e3edbc9322792f7ab2e" +source = "git+https://github.com/dojoengine/blockifier?rev=f7df9ba#f7df9ba98a8e0549813fbde945741fd50930ff29" dependencies = [ "ark-ff", "ark-secp256k1", @@ -734,8 +692,8 @@ dependencies = [ "sha3", "starknet-crypto 0.5.1", "starknet_api", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "thiserror", ] @@ -762,12 +720,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" dependencies = [ "memchr", - "regex-automata 0.3.6", + "regex-automata 0.3.8", "serde", ] @@ -782,9 +740,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byte-slice-cast" @@ -800,18 +758,18 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] [[package]] name = "bytesize" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38fcc2979eff34a4b84e1cf9a1e3da42a7d44b3b690a40cdcb23e3d556cfb2e5" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "cairo-felt" @@ -827,12 +785,12 @@ dependencies = [ [[package]] name = "cairo-lang-casm" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213103e1cf9049abd443f97088939d9cf6ef5500b295003be2b244c702d8fe9c" +checksum = "afc7f7cb89bc3f52c2c738f3e87c8f8773bd3456cae1d322d100d4b0da584f3c" dependencies = [ "cairo-lang-utils", - "indoc 2.0.3", + "indoc 2.0.4", "num-bigint", "num-traits 0.2.16", "parity-scale-codec", @@ -844,9 +802,9 @@ dependencies = [ [[package]] name = "cairo-lang-compiler" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb857feb6d7a73fd33193a2cc141c04ab345d47bcbd9e2c014ef3422ebc6d55" +checksum = "d4f2c54b065f7fd97bf8d5df76cbcbbd01d8a8c319d281796ee20ecc48e16ca8" dependencies = [ "anyhow", "cairo-lang-defs", @@ -861,6 +819,7 @@ dependencies = [ "cairo-lang-sierra-generator", "cairo-lang-syntax", "cairo-lang-utils", + "itertools 0.11.0", "log", "salsa", "smol_str", @@ -869,18 +828,18 @@ dependencies = [ [[package]] name = "cairo-lang-debug" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af06d0c89bd515707d6f0140a880f6463b955189fa5f97719edd62348c36ee2c" +checksum = "873ba77d4c3f780c727c7d6c738cded22b3f6d4023e30546dfe14f97a087887e" dependencies = [ "cairo-lang-utils", ] [[package]] name = "cairo-lang-defs" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ead2773a4d8147c3c25666d9a97a41161eeb59e0d0d1060b5f9b6fa68d0da1" +checksum = "f5031fff038c27ed43769b73a6f5d41aeaea34df9af862e024c23fbb4f076249" dependencies = [ "cairo-lang-debug", "cairo-lang-diagnostics", @@ -896,9 +855,9 @@ dependencies = [ [[package]] name = "cairo-lang-diagnostics" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6924bc3d558495a5327955da3aec15e6ab71c0f83e955258ffbb0e1a20ead87" +checksum = "7b6cb1492e5784e1076320a5018ce7584f391b2f3b414bc0a8ab7c289fa118ce" dependencies = [ "cairo-lang-debug", "cairo-lang-filesystem", @@ -909,9 +868,9 @@ dependencies = [ [[package]] name = "cairo-lang-eq-solver" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac8853dae37b4f3b4d3d8d36002b2fb0b52aa5f578f15b0bf47cedd2ec7358b" +checksum = "c35dddbc63b2a4870891cc74498726aa32bfaa518596352f9bb101411cc4c584" dependencies = [ "cairo-lang-utils", "good_lp", @@ -921,9 +880,9 @@ dependencies = [ [[package]] name = "cairo-lang-filesystem" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb3fc0d5582fd27910e63cca94a7c9d41acc0045a815ff99ed941eb45183375" +checksum = "32ce0b8e66a6085ae157d43b5c162d60166f0027d6f125c50ee74e4dc7916ff6" dependencies = [ "cairo-lang-debug", "cairo-lang-utils", @@ -935,9 +894,9 @@ dependencies = [ [[package]] name = "cairo-lang-formatter" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cfcc3d641766fd931432ae489ca73a8fb7edf83f2535faf6313de99666814e3" +checksum = "79535d235d17f3be2a2d7e82b92709ed4cb60978e8d96c92520178a795162969" dependencies = [ "anyhow", "cairo-lang-diagnostics", @@ -956,9 +915,9 @@ dependencies = [ [[package]] name = "cairo-lang-language-server" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277390f668e6c21daec570dac01587455bac488455cdc029fc9503cb0c098f3d" +checksum = "e4793c9799857c94ddd8f7146220fd82b4a4402fa8597f49d48b705747f9ee05" dependencies = [ "anyhow", "cairo-lang-compiler", @@ -975,11 +934,11 @@ dependencies = [ "cairo-lang-starknet", "cairo-lang-syntax", "cairo-lang-utils", - "indoc 2.0.3", + "indoc 2.0.4", "log", "lsp-types", "salsa", - "scarb-metadata 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scarb-metadata 1.8.0", "serde", "serde_json", "smol_str", @@ -989,9 +948,9 @@ dependencies = [ [[package]] name = "cairo-lang-lowering" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d7f5acc6e4c67a6f2232df1d1f13039453cb50d2971183a1fd254067735cd0" +checksum = "29cc679f501725e03ee703559ed27d084c6f4031bd51ff86378cf845a85ee207" dependencies = [ "cairo-lang-debug", "cairo-lang-defs", @@ -1008,15 +967,16 @@ dependencies = [ "log", "num-bigint", "num-traits 0.2.16", + "once_cell", "salsa", "smol_str", ] [[package]] name = "cairo-lang-parser" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9716a807fd4430a4af396ad9f7bd1228414c972a24a7e4211ac83d83f538d1" +checksum = "cdcadb046659134466bc7e11961ea8a56969dae8a54d8f985955ce0b95185c7f" dependencies = [ "cairo-lang-diagnostics", "cairo-lang-filesystem", @@ -1035,18 +995,18 @@ dependencies = [ [[package]] name = "cairo-lang-plugins" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb782377b1cb01ccb3e00aec3903912895ae3cf6b5a8e05918bc0e4a837c1929" +checksum = "4632790cd4ea11d4849934456a400eae7ed419f6d721f24a6b637df67b7e902f" dependencies = [ "cairo-lang-defs", "cairo-lang-diagnostics", "cairo-lang-filesystem", "cairo-lang-parser", - "cairo-lang-semantic", "cairo-lang-syntax", "cairo-lang-utils", - "indoc 2.0.3", + "indent", + "indoc 2.0.4", "itertools 0.11.0", "num-bigint", "salsa", @@ -1055,20 +1015,20 @@ dependencies = [ [[package]] name = "cairo-lang-proc-macros" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756fe3fdb86d3d8fda40ee250d146300381e98110ec7e4a624407c7e0b7e63f" +checksum = "170838817fc33ddb65e0a9480526df0b226b148a0fca0a5cd7071be4c6683157" dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "cairo-lang-project" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "913cdcd5e567d7ca13d1b37822feeac19973f5133a4fbab76b67e12c847bff3a" +checksum = "4162ee976c61fdeb3b621f4a76fd256e46a5c0890f750a3a9d2c9560a3bc1daf" dependencies = [ "cairo-lang-filesystem", "cairo-lang-utils", @@ -1080,9 +1040,9 @@ dependencies = [ [[package]] name = "cairo-lang-runner" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ce3aa736c0180b5ed5320138b3f8ed70696618f2cb23bc86217eebfa2274ea" +checksum = "11d66ef01350e2e7f7e6b2b43b865da2513a42600082ee1a2975d3af3da7f0ca" dependencies = [ "anyhow", "ark-ff", @@ -1117,15 +1077,16 @@ dependencies = [ [[package]] name = "cairo-lang-semantic" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6962d05da3c355f8fc49e97dda4252dfa71969399ef5658130b9cdc86d8a805" +checksum = "13e544fa9a222bf2d007df2b5fc9b21c2a20ab7e17d6fefbcbc193de209451cd" dependencies = [ "cairo-lang-debug", "cairo-lang-defs", "cairo-lang-diagnostics", "cairo-lang-filesystem", "cairo-lang-parser", + "cairo-lang-plugins", "cairo-lang-proc-macros", "cairo-lang-syntax", "cairo-lang-utils", @@ -1134,15 +1095,16 @@ dependencies = [ "log", "num-bigint", "num-traits 0.2.16", + "once_cell", "salsa", "smol_str", ] [[package]] name = "cairo-lang-sierra" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872cf03415aa48c7757e4cee4b0223a158fcc9abddf55574f6c387324f25cc1f" +checksum = "d5e136b79e95a14ef38a2be91a67ceb85317407d336a5b0d418c33b23c78596a" dependencies = [ "cairo-lang-utils", "const-fnv1a-hash", @@ -1163,9 +1125,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-ap-change" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eba2fbd5f13210a46c2050fe156776490c556f6f0335401bda810640c1e65fd" +checksum = "511ca7708faa7ba8d14ae26e1d60ead2d02028c8f664baf5ecb0fd6a0d1e20f6" dependencies = [ "cairo-lang-eq-solver", "cairo-lang-sierra", @@ -1177,9 +1139,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-gas" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9f4cec68f861b86dd6429592d9c0c535cbb983a9b7c21fc906b659b35e7882e" +checksum = "351a25bc010b910919c01d5c57e937b0c3d330fc30d92702c0cb4061819df8df" dependencies = [ "cairo-lang-eq-solver", "cairo-lang-sierra", @@ -1191,9 +1153,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-generator" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f050d6c717090ee10be52950cfe639709af27138eb9cd4122b0ab171a5cf2a" +checksum = "114091bb971c06fd072aca816af1c3f62566cd8a4b1453c786155161a36c7bce" dependencies = [ "cairo-lang-debug", "cairo-lang-defs", @@ -1211,15 +1173,16 @@ dependencies = [ "indexmap 2.0.0", "itertools 0.11.0", "num-bigint", + "once_cell", "salsa", "smol_str", ] [[package]] name = "cairo-lang-sierra-to-casm" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c53fa4c6013827c42c1e02ee9a58fa5901cb18a711bdfeb1af379f69d2055c" +checksum = "fa1c799de62972dfd7112d563000695be94305b6f7d9bedd29f347799bf03e1c" dependencies = [ "assert_matches", "cairo-felt", @@ -1229,7 +1192,7 @@ dependencies = [ "cairo-lang-sierra-gas", "cairo-lang-sierra-type-size", "cairo-lang-utils", - "indoc 2.0.3", + "indoc 2.0.4", "itertools 0.11.0", "log", "num-bigint", @@ -1239,9 +1202,9 @@ dependencies = [ [[package]] name = "cairo-lang-sierra-type-size" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07a5e70b5a5826edeb61ec375886847f51e1b995709e3f36d657844fbd703d45" +checksum = "d2fe73d9d58aaf9088f6ba802bcf43ce9ca4bd198190cf5bf91caa7d408dd11a" dependencies = [ "cairo-lang-sierra", "cairo-lang-utils", @@ -1249,9 +1212,9 @@ dependencies = [ [[package]] name = "cairo-lang-starknet" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfaa6629cd5a9cc13543d59bd5494fc01cc4118efbcc5b13528be4539a35f77f" +checksum = "75df624e71e33a31a924e799dd2a9a8284204b41d8db9c51803317bd9edff81f" dependencies = [ "anyhow", "cairo-felt", @@ -1274,7 +1237,7 @@ dependencies = [ "convert_case 0.6.0", "genco", "indent", - "indoc 2.0.3", + "indoc 2.0.4", "itertools 0.11.0", "log", "num-bigint", @@ -1290,9 +1253,9 @@ dependencies = [ [[package]] name = "cairo-lang-syntax" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23b312f07e45bc0bb2d240187a37db2816393c285896c9ab453c8ca8e128d25" +checksum = "0b1af0ae21f9e539f97cfdf56f5ce0934dae5d87f568fd778c3d624a102f8dbb" dependencies = [ "cairo-lang-debug", "cairo-lang-filesystem", @@ -1307,9 +1270,9 @@ dependencies = [ [[package]] name = "cairo-lang-syntax-codegen" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0225e4b5f09523bc424fae216ea899cb8f9132fb0da164096cee5e2ce79076fe" +checksum = "822ffabf24f6a5506262edcece315260a82d9dfba3abe6548791a6d654563ad0" dependencies = [ "genco", "xshell", @@ -1317,9 +1280,9 @@ dependencies = [ [[package]] name = "cairo-lang-test-runner" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e991757d316fe1af4d99df1ebfba666c713284f857ef2b282c5f4077236182f6" +checksum = "43323524d8bcae023e66c9c6f3e857ae73cddc86bb2cc7ea6d739ef54beacb19" dependencies = [ "anyhow", "cairo-felt", @@ -1345,27 +1308,28 @@ dependencies = [ "itertools 0.11.0", "num-bigint", "num-traits 0.2.16", - "rayon", + "rayon 1.8.0", "salsa", "thiserror", ] [[package]] name = "cairo-lang-test-utils" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a612e186c7eddc8f82951c6ffef7c4fdf7ee9b0a5cb7a46f06bae990fe717574" +checksum = "81394b73748176c2485cc8e25be8163ab883f7076efb261a50b0d2fdf7d41fb8" dependencies = [ "cairo-lang-utils", + "colored", "log", "pretty_assertions", ] [[package]] name = "cairo-lang-utils" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4bdb1d6509e2579d04d1757070f88c2542ff033194f749772669a1615c7e4" +checksum = "f974b6e859f0b09c0f13ec8188c96e9e8bbb5da04214f911dbb5bcda67cb812b" dependencies = [ "env_logger", "indexmap 2.0.0", @@ -1377,7 +1341,7 @@ dependencies = [ "parity-scale-codec", "schemars", "serde", - "time 0.3.25", + "time", ] [[package]] @@ -1443,9 +1407,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", @@ -1459,18 +1423,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits 0.2.16", "serde", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets 0.48.5", ] [[package]] @@ -1485,13 +1448,12 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.21" +version = "4.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] @@ -1506,9 +1468,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.21" +version = "4.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab" dependencies = [ "anstream", "anstyle", @@ -1518,30 +1480,30 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.3.2" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +checksum = "8baeccdb91cd69189985f87f3c7e453a3a451ab5746cf3be6acc92120bd16d24" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clru" @@ -1566,15 +1528,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "console" version = "0.15.7" @@ -1661,7 +1614,7 @@ dependencies = [ [[package]] name = "create-output-dir" version = "1.0.0" -source = "git+https://github.com/software-mansion/scarb?rev=c07fa61#c07fa61553985f045286166d0235e55492694159" +source = "git+https://github.com/software-mansion/scarb?rev=7adb7fd#7adb7fd972e4ec95dc404650dec78099815392c9" dependencies = [ "anyhow", "core-foundation", @@ -1744,11 +1697,12 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" dependencies = [ "generic-array", + "rand_core", "subtle", "zeroize", ] @@ -1765,12 +1719,12 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f34ba9a9bcb8645379e9de8cb3ecfcf4d1c85ba66d90deb3259206fa5aa193b" +checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" dependencies = [ "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -1784,9 +1738,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ "nix", "windows-sys 0.48.0", @@ -1837,7 +1791,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -1859,14 +1813,14 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "dashmap" -version = "5.5.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.0", @@ -1899,9 +1853,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" dependencies = [ "serde", ] @@ -2057,6 +2011,7 @@ dependencies = [ "anyhow", "assert_fs", "cairo-lang-compiler", + "cairo-lang-debug", "cairo-lang-defs", "cairo-lang-diagnostics", "cairo-lang-filesystem", @@ -2079,6 +2034,7 @@ dependencies = [ "env_logger", "indoc 1.0.9", "itertools 0.10.5", + "once_cell", "pretty_assertions", "salsa", "sanitizer", @@ -2137,10 +2093,12 @@ dependencies = [ "cairo-lang-starknet", "camino", "dojo-lang", + "dojo-world", "jsonrpsee", "katana-core", "katana-rpc", "scarb", + "scarb-ui", "serde", "serde_json", "serde_with", @@ -2157,8 +2115,12 @@ dependencies = [ name = "dojo-types" version = "0.2.1" dependencies = [ + "hex", "serde", "starknet", + "strum 0.25.0", + "strum_macros 0.25.2", + "thiserror", ] [[package]] @@ -2185,7 +2147,7 @@ dependencies = [ "serde_with", "smol_str", "starknet", - "starknet-crypto 0.5.1", + "starknet-crypto 0.6.0", "thiserror", "tokio", "toml", @@ -2207,9 +2169,9 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" +checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" [[package]] name = "either" @@ -2237,9 +2199,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -2265,9 +2227,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -2348,11 +2310,20 @@ dependencies = [ "ascii_utils", ] +[[package]] +name = "faster-hex" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239f7bfb930f820ab16a9cd95afc26f88264cf6905c960b340a615384aa3338a" +dependencies = [ + "serde", +] + [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "filetime" @@ -2366,6 +2337,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2431,6 +2408,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" @@ -2504,7 +2490,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -2539,9 +2525,9 @@ dependencies = [ [[package]] name = "genco" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6973ce8518068a71d404f428f6a5b563088545546a6bd8f9c0a7f2608149bc8a" +checksum = "3597f99dbe04460775cb349299b9532123980b17d89faeaa2da42658b7767787" dependencies = [ "genco-macros", "relative-path", @@ -2550,9 +2536,9 @@ dependencies = [ [[package]] name = "genco-macros" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2c778cf01917d0fbed53900259d6604a421fab4916a2e738856ead9f1d926a" +checksum = "b029ca4c73c30f813e0e92754515585ccbede98014fb26644cc7488a3833706a" dependencies = [ "proc-macro2", "quote", @@ -2578,15 +2564,15 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "gix" @@ -2685,9 +2671,9 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa8bbde7551a9e3e783a2871f53bbb0f50aac7a77db5680c8709f69e8ce724f" +checksum = "0ccab4bc576844ddb51b78d81b4a42d73e6229660fa614dfc3d3999c874d1959" dependencies = [ "thiserror", ] @@ -2703,9 +2689,9 @@ dependencies = [ [[package]] name = "gix-command" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2783ad148fb16bf9cfd46423706ba552a62a4d4a18fda5dd07648eb0228862dd" +checksum = "0f28f654184b5f725c5737c7e4f466cbd8f0102ac352d5257eeab19647ee4256" dependencies = [ "bstr", ] @@ -2777,14 +2763,14 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f7c76578a69b736c3f0770f14757e9027354011d24c56d79207add9d7d1be6" +checksum = "0a825babda995d788e30d306a49dacd1e93d5f5d33d53c7682d0347cef40333c" dependencies = [ "bstr", "itoa", "thiserror", - "time 0.3.25", + "time", ] [[package]] @@ -3037,12 +3023,12 @@ dependencies = [ [[package]] name = "gix-packetline-blocking" -version = "0.16.4" +version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20276373def40fc3be7a86d09e1bb607d33dd6bf83e3504e83cd594e51438667" +checksum = "7d8395f7501c84d6a1fe902035fdfd8cd86d89e2dd6be0200ec1a72fd3c92d39" dependencies = [ "bstr", - "hex", + "faster-hex", "thiserror", ] @@ -3074,9 +3060,9 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfd80d3d0c733508df9449b1d3795da36083807e31d851d7d61d29af13bd4b0a" +checksum = "475c86a97dd0127ba4465fbb239abac9ea10e68301470c9791a6dd5351cdc905" dependencies = [ "bstr", "btoi", @@ -3302,9 +3288,9 @@ dependencies = [ [[package]] name = "good_lp" -version = "1.4.2" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473d618f0f2c16d1ecb73bc755b207150665fd6b3e5b9570313cee6bba3880db" +checksum = "869f19637130a4e8e1c3f3f83df4a00a169c1d3a77a2b2ff41736b14497c4027" dependencies = [ "fnv", "minilp", @@ -3312,9 +3298,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -3331,9 +3317,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "4.3.7" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" dependencies = [ "log", "pest", @@ -3374,9 +3360,9 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ "hashbrown 0.14.0", ] @@ -3393,12 +3379,11 @@ dependencies = [ [[package]] name = "headers" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", + "base64 0.21.4", "bytes", "headers-core", "http", @@ -3436,9 +3421,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -3549,7 +3534,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.6", + "rustls 0.21.7", "tokio", "tokio-rustls 0.24.1", ] @@ -3725,9 +3710,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", "instant", @@ -3744,9 +3729,29 @@ checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "indoc" -version = "2.0.3" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] [[package]] name = "inout" @@ -3847,9 +3852,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d291e3a5818a2384645fd9756362e6d89cf0541b0b916fa7702ea4a9833608e" +checksum = "367a292944c07385839818bb71c8d76611138e2dedb0677d035b8da21d29c78b" dependencies = [ "jsonrpsee-core", "jsonrpsee-proc-macros", @@ -3860,9 +3865,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e70b4439a751a5de7dd5ed55eacff78ebf4ffe0fc009cb1ebb11417f5b536b" +checksum = "2b5dde66c53d6dcdc8caea1874a45632ec0fcf5b437789f1e45766a1512ce803" dependencies = [ "anyhow", "arrayvec", @@ -3886,9 +3891,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa6da1e4199c10d7b1d0a6e5e8bd8e55f351163b6f4b3cbb044672a69bd4c1c" +checksum = "44e8ab85614a08792b9bff6c8feee23be78c98d0182d4c622c05256ab553892a" dependencies = [ "heck 0.4.1", "proc-macro-crate", @@ -3899,9 +3904,9 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb69dad85df79527c019659a992498d03f8495390496da2f07e6c24c2b356fc" +checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba" dependencies = [ "futures-channel", "futures-util", @@ -3921,9 +3926,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd522fe1ce3702fd94812965d7bb7a3364b1c9aba743944c5a00529aae80f8c" +checksum = "245ba8e5aa633dd1c1e4fae72bce06e71f42d34c14a2767c6b4d173b57bee5e5" dependencies = [ "anyhow", "beef", @@ -3940,7 +3945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" dependencies = [ "crossbeam", - "rayon", + "rayon 1.8.0", ] [[package]] @@ -3951,12 +3956,13 @@ dependencies = [ "clap", "clap_complete", "console", - "env_logger", "katana-core", "katana-rpc", - "log", "starknet_api", "tokio", + "tracing", + "tracing-subscriber", + "url", ] [[package]] @@ -3965,8 +3971,6 @@ version = "0.2.1" dependencies = [ "anyhow", "assert_matches", - "async-trait", - "auto_impl", "blockifier", "cairo-lang-casm", "cairo-lang-starknet", @@ -3985,6 +3989,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "url", ] [[package]] @@ -3998,6 +4003,7 @@ dependencies = [ "cairo-vm", "dojo-test-utils", "flate2", + "futures", "hex", "hyper", "jsonrpsee", @@ -4024,6 +4030,26 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kstring" version = "2.0.0" @@ -4049,7 +4075,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", "string_cache", "term", "tiny-keccak", @@ -4076,15 +4102,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libmimalloc-sys" -version = "0.1.33" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ac0e912c8ef1b735e92369695618dc5b1819f5a7bf3f167301a3ba1cea515e" +checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" dependencies = [ "cc", "libc", @@ -4109,9 +4135,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -4171,9 +4197,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "matrixmultiply" @@ -4186,9 +4212,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memmap2" @@ -4210,9 +4236,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.37" +version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2894987a3459f3ffb755608bd82188f8ed00d0ae077f1edea29c068d639d98" +checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" dependencies = [ "libmimalloc-sys", ] @@ -4265,7 +4291,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "log", + "wasi", "windows-sys 0.48.0", ] @@ -4320,14 +4347,13 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nix" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "cfg-if", "libc", - "static_assertions", ] [[package]] @@ -4346,6 +4372,35 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "notify-debouncer-mini" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55ee272914f4563a2f8b8553eb6811f3c0caea81c756346bad15b7e3ef969f0" +dependencies = [ + "crossbeam-channel", + "notify", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -4358,9 +4413,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -4461,9 +4516,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -4525,9 +4580,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parity-scale-codec" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" dependencies = [ "arrayvec", "bitvec", @@ -4539,9 +4594,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -4594,7 +4649,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets 0.48.2", + "windows-targets 0.48.5", ] [[package]] @@ -4611,9 +4666,9 @@ checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" [[package]] name = "path-dedot" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d55e486337acb9973cdea3ec5638c1b3bcb22e573b2b7b41969e0c744d5a15e" +checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397" dependencies = [ "once_cell", ] @@ -4644,19 +4699,20 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" dependencies = [ "pest", "pest_generator", @@ -4664,22 +4720,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" dependencies = [ "once_cell", "pest", @@ -4688,12 +4744,12 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 1.9.3", + "indexmap 2.0.0", ] [[package]] @@ -4726,7 +4782,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -4749,14 +4805,14 @@ dependencies = [ [[package]] name = "phonenumber" -version = "0.3.2+8.13.9" +version = "0.3.3+8.13.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34749f64ea9d76f10cdc8a859588b57775f59177c7dd91f744d620bd62982d6f" +checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06" dependencies = [ "bincode 1.3.3", "either", "fnv", - "itertools 0.10.5", + "itertools 0.11.0", "lazy_static", "nom", "quick-xml", @@ -4764,6 +4820,7 @@ dependencies = [ "regex-cache", "serde", "serde_derive", + "strum 0.24.1", "thiserror", ] @@ -4790,14 +4847,14 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -4813,12 +4870,11 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "poem" -version = "1.3.57" +version = "1.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d92c532a37a9e98c0e9a0411e6852b8acccf9ec07d5e6e450b01cbf947d90b" +checksum = "ebc7ae19f3e791ae8108b08801abb3708d64d3a16490c720e0b81040cae87b5d" dependencies = [ "async-trait", - "base64 0.21.2", "bytes", "futures-util", "headers", @@ -4837,28 +4893,27 @@ dependencies = [ "smallvec", "thiserror", "tokio", - "tokio-tungstenite", "tokio-util", "tracing", ] [[package]] name = "poem-derive" -version = "1.3.57" +version = "1.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5dd58846a1f582215370384c3090c62c9ef188e9d798ffc67ea90d0a1a8a3b8" +checksum = "2550a0bce7273b278894ef3ccc5a6869e7031b6870042f3cc6826ed9faa980a6" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "portable-atomic" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] name = "ppv-lite86" @@ -4874,13 +4929,13 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" +checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" dependencies = [ "anstyle", "difflib", - "itertools 0.10.5", + "itertools 0.11.0", "predicates-core", ] @@ -4920,6 +4975,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.37", +] + [[package]] name = "primitive-types" version = "0.12.1" @@ -4969,18 +5034,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "25.0.1" +version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c236e70b7f9b9ea00d33c69f63ec1ae6e9ae96118923cd37bd4e9c7396f0b107" +checksum = "1d67eb4220992a4a052a4bb03cf776e493ecb1a3a36bab551804153d63486af7" dependencies = [ "bytesize", "human_format", @@ -4993,7 +5058,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +dependencies = [ + "bytes", + "prost-derive 0.12.1", ] [[package]] @@ -5009,15 +5084,37 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", - "prost", - "prost-types", + "prettyplease 0.1.25", + "prost 0.11.9", + "prost-types 0.11.9", "regex", "syn 1.0.109", "tempfile", "which", ] +[[package]] +name = "prost-build" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools 0.11.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease 0.2.15", + "prost 0.12.1", + "prost-types 0.12.1", + "regex", + "syn 2.0.37", + "tempfile", + "which", +] + [[package]] name = "prost-derive" version = "0.11.9" @@ -5031,13 +5128,35 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "prost-types" version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "prost", + "prost 0.11.9", +] + +[[package]] +name = "prost-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +dependencies = [ + "prost 0.12.1", ] [[package]] @@ -5051,9 +5170,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -5102,9 +5221,19 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "ed02d09394c94ffbdfdc755ad62a132e94c3224a8354e78a1200ced34df12edf" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -5112,14 +5241,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -5153,14 +5280,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.6", - "regex-syntax 0.7.4", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", ] [[package]] @@ -5174,13 +5301,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", ] [[package]] @@ -5203,23 +5330,23 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "relative-path" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf2521270932c3c7bed1a59151222bd7643c79310f2916f01925e1e16255698" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -5236,7 +5363,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.6", + "rustls 0.21.7", "rustls-pemfile", "serde", "serde_json", @@ -5248,7 +5375,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.2", "winreg", ] @@ -5325,9 +5452,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.7" +version = "0.38.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" dependencies = [ "bitflags 2.4.0", "errno", @@ -5338,9 +5465,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring", @@ -5350,9 +5477,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.6" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", @@ -5366,14 +5493,14 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", ] [[package]] name = "rustls-webpki" -version = "0.101.3" +version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ "ring", "untrusted", @@ -5464,12 +5591,13 @@ dependencies = [ [[package]] name = "scarb" -version = "0.6.2" -source = "git+https://github.com/software-mansion/scarb?rev=c07fa61#c07fa61553985f045286166d0235e55492694159" +version = "0.7.0" +source = "git+https://github.com/software-mansion/scarb?rev=7adb7fd#7adb7fd972e4ec95dc404650dec78099815392c9" dependencies = [ "anyhow", "async-trait", "cairo-lang-compiler", + "cairo-lang-defs", "cairo-lang-filesystem", "cairo-lang-formatter", "cairo-lang-semantic", @@ -5479,7 +5607,6 @@ dependencies = [ "camino", "clap", "clap-verbosity-flag", - "console", "create-output-dir", "data-encoding", "deno_task_shell", @@ -5493,14 +5620,14 @@ dependencies = [ "glob", "ignore", "include_dir", - "indicatif", - "indoc 2.0.3", + "indoc 2.0.4", "itertools 0.11.0", "once_cell", "pathdiff", "petgraph", "scarb-build-metadata", - "scarb-metadata 1.6.0 (git+https://github.com/software-mansion/scarb?rev=c07fa61)", + "scarb-metadata 1.7.0", + "scarb-ui", "semver", "serde", "serde-value", @@ -5511,7 +5638,6 @@ dependencies = [ "toml", "toml_edit", "tracing", - "tracing-futures", "tracing-log", "tracing-subscriber", "typed-builder", @@ -5524,19 +5650,19 @@ dependencies = [ [[package]] name = "scarb-build-metadata" -version = "0.6.2" -source = "git+https://github.com/software-mansion/scarb?rev=c07fa61#c07fa61553985f045286166d0235e55492694159" +version = "0.7.0" +source = "git+https://github.com/software-mansion/scarb?rev=7adb7fd#7adb7fd972e4ec95dc404650dec78099815392c9" dependencies = [ "cargo_metadata", ] [[package]] name = "scarb-metadata" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60d9d2ce9f8c720c096da2f6c0d08fa6ce3123fef5d72c79ce2d0c9491f92cc" +version = "1.7.0" +source = "git+https://github.com/software-mansion/scarb?rev=7adb7fd#7adb7fd972e4ec95dc404650dec78099815392c9" dependencies = [ "camino", + "derive_builder", "semver", "serde", "serde_json", @@ -5545,23 +5671,38 @@ dependencies = [ [[package]] name = "scarb-metadata" -version = "1.6.0" -source = "git+https://github.com/software-mansion/scarb?rev=c07fa61#c07fa61553985f045286166d0235e55492694159" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1939d4a36ba77bc4a23024f42e16e307b74fe451d8efa23aff8916fdde6a83" dependencies = [ "camino", - "clap", - "derive_builder", "semver", "serde", "serde_json", "thiserror", ] +[[package]] +name = "scarb-ui" +version = "0.7.0" +source = "git+https://github.com/software-mansion/scarb?rev=7adb7fd#7adb7fd972e4ec95dc404650dec78099815392c9" +dependencies = [ + "anyhow", + "camino", + "clap", + "console", + "indicatif", + "indoc 2.0.4", + "scarb-metadata 1.7.0", + "serde", + "serde_json", +] + [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" dependencies = [ "dyn-clone", "indexmap 1.9.3", @@ -5572,9 +5713,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" dependencies = [ "proc-macro2", "quote", @@ -5582,6 +5723,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -5612,18 +5759,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -5640,13 +5787,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -5662,9 +5809,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -5690,7 +5837,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -5727,7 +5874,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.25", + "time", ] [[package]] @@ -5739,7 +5886,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -5757,9 +5904,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -5774,9 +5921,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -5829,24 +5976,24 @@ checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smol_str" @@ -5859,9 +6006,9 @@ dependencies = [ [[package]] name = "snapbox" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bccd62078347f89a914e3004d94582e13824d4e3d8a816317862884c423835" +checksum = "ad90eb3a2e3a8031d636d45bd4832751aefd58a291b553f7305a2bacae21aff3" dependencies = [ "anstream", "anstyle", @@ -5872,9 +6019,9 @@ dependencies = [ [[package]] name = "snapbox-macros" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaaf09df9f0eeae82be96290918520214530e738a7fe5a351b0f24cf77c0ca31" +checksum = "95f4ffd811b87da98d0e48285134b7847954bd76e843bb794a893b47ca3ee325" dependencies = [ "anstream", ] @@ -5891,9 +6038,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -5923,6 +6070,7 @@ dependencies = [ "assert_fs", "async-trait", "cairo-lang-compiler", + "cairo-lang-defs", "cairo-lang-filesystem", "cairo-lang-plugins", "cairo-lang-project", @@ -5937,9 +6085,12 @@ dependencies = [ "console", "dojo-lang", "dojo-test-utils", + "dojo-types", "dojo-world", - "log", + "notify", + "notify-debouncer-mini", "scarb", + "scarb-ui", "semver", "serde", "serde_json", @@ -5982,11 +6133,11 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ - "itertools 0.10.5", + "itertools 0.11.0", "nom", "unicode_categories", ] @@ -6035,7 +6186,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.20.8", + "rustls 0.20.9", "rustls-pemfile", "serde", "sha2", @@ -6047,7 +6198,7 @@ dependencies = [ "tokio-stream", "url", "uuid 1.4.1", - "webpki-roots", + "webpki-roots 0.22.6", ] [[package]] @@ -6085,9 +6236,9 @@ dependencies = [ [[package]] name = "starknet" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcb61961b91757a9bc2d11549067445b2f921bd957f53710db35449767a1ba3" +checksum = "6f0623b045f3dc10aef030c9ddd4781cff9cbe1188b71063cc510b75d1f96be6" dependencies = [ "starknet-accounts", "starknet-contract", @@ -6101,11 +6252,12 @@ dependencies = [ [[package]] name = "starknet-accounts" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111ed887e4db14f0df1f909905e7737e4730770c8ed70997b58a71d5d940daac" +checksum = "68e97edc480348dca300e5a8234e6c4e6f2f1ac028f2b16fcce294ebe93d07f4" dependencies = [ "async-trait", + "auto_impl", "starknet-core", "starknet-providers", "starknet-signers", @@ -6114,9 +6266,9 @@ dependencies = [ [[package]] name = "starknet-contract" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d6f81a647694b2cb669ab60e77954b57bf5fbc757f5fcaf0a791c3bd341f04" +checksum = "69b86e3f6b3ca9a5c45271ab10871c99f7dc82fee3199d9f8c7baa2a1829947d" dependencies = [ "serde", "serde_json", @@ -6129,11 +6281,11 @@ dependencies = [ [[package]] name = "starknet-core" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91f89c79b641618de8aa9668d74c6b6634659ceca311c6318a35c025f9d4d969" +checksum = "b796a32a7400f7d85e95d3900b5cee7a392b2adbf7ad16093ed45ec6f8d85de6" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "flate2", "hex", "serde", @@ -6193,7 +6345,7 @@ checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" dependencies = [ "starknet-curve 0.4.0", "starknet-ff", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -6231,19 +6383,19 @@ dependencies = [ [[package]] name = "starknet-macros" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a5865ee0ed22ade86bdf45e7c09c5641f1c59ccae12c21ecde535b2b6bf64a" +checksum = "ef846b6bb48fc8c3e9a2aa9b5b037414f04a908d9db56493a3ae69a857eb2506" dependencies = [ "starknet-core", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "starknet-providers" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbfccb46a8969fb3ac803718d9d8270cff4eed5b7f6b9ba234875ad2cc997c5" +checksum = "c3b136c26b72ff1756f0844e0aa80bab680ceb99d63921826facbb8e7340ff82" dependencies = [ "async-trait", "auto_impl", @@ -6261,9 +6413,9 @@ dependencies = [ [[package]] name = "starknet-signers" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313524cc79344015ef2a8618947332ab17012b5c50600c7f84c60989bdec980" +checksum = "d9386015d2e6dc3df285bfb33a3afd8ad7596c70ed38ab57019de4d2dfc7826f" dependencies = [ "async-trait", "auto_impl", @@ -6313,10 +6465,11 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -6332,6 +6485,15 @@ name = "strum" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" @@ -6346,6 +6508,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.37", +] + [[package]] name = "subtle" version = "2.5.0" @@ -6365,9 +6540,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -6388,9 +6563,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.7.1" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", @@ -6412,9 +6587,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] @@ -6438,22 +6613,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.46" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9207952ae1a003f42d3d5e892dac3c6ba42aa6ac0c79a6a91a2b5cb4253e75c" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.46" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1728216d3244de4f14f14f8c15c79be1a7c67867d28d69b719690e2a19fb445" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -6488,20 +6663,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", "itoa", @@ -6514,15 +6678,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -6553,9 +6717,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.31.0" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ "backtrace", "bytes", @@ -6565,7 +6729,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.4", "tokio-macros", "windows-sys 0.48.0", ] @@ -6588,7 +6752,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -6597,7 +6761,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.8", + "rustls 0.20.9", "tokio", "webpki", ] @@ -6608,7 +6772,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.6", + "rustls 0.21.7", "tokio", ] @@ -6625,9 +6789,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.19.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", @@ -6637,9 +6801,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -6652,9 +6816,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", "serde_spanned", @@ -6673,9 +6837,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", "serde", @@ -6691,11 +6855,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", - "axum", - "base64 0.21.2", + "base64 0.21.4", "bytes", + "flate2", "futures-core", "futures-util", + "http", + "http-body", + "percent-encoding", + "pin-project", + "prost 0.11.9", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c00bc15e49625f3d2f20b17082601e5e17cf27ead69e805174026c194b6664" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.4", + "bytes", "h2", "http", "http-body", @@ -6703,7 +6889,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost", + "prost 0.12.1", "tokio", "tokio-stream", "tower", @@ -6718,30 +6904,97 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" dependencies = [ - "prettyplease", + "prettyplease 0.1.25", "proc-macro2", - "prost-build", + "prost-build 0.11.9", "quote", "syn 1.0.109", ] +[[package]] +name = "tonic-build" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d37bb15da06ae9bb945963066baca6561b505af93a52e949a85d28558459a2" +dependencies = [ + "prettyplease 0.2.15", + "proc-macro2", + "prost-build 0.12.1", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tonic-web" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2953fe95664e86519e0d1c4bdd65007d93bc47a59c9af512280977aa9e46b871" +dependencies = [ + "base64 0.21.4", + "bytes", + "http", + "http-body", + "hyper", + "pin-project", + "tokio-stream", + "tonic 0.10.1", + "tower-http", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-web-wasm-client" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5987e92915a51a4b05e69a0ef903a7b76f16674f7ee66534f87fd3323e2d3a" +dependencies = [ + "base64 0.21.4", + "byteorder", + "bytes", + "futures-util", + "http", + "http-body", + "httparse", + "js-sys", + "pin-project", + "thiserror", + "tonic 0.9.2", + "tower-service", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + [[package]] name = "torii-client" version = "0.2.1" dependencies = [ - "async-std", + "anyhow", "async-trait", "camino", + "crypto-bigint", "dojo-test-utils", "dojo-types", "dojo-world", + "futures", + "futures-util", "js-sys", "parking_lot 0.12.1", + "prost 0.11.9", + "prost 0.12.1", "serde", + "serde_json", "starknet", - "starknet-crypto 0.5.1", + "starknet-crypto 0.6.0", "thiserror", "tokio", + "tonic 0.10.1", + "tonic 0.9.2", + "torii-grpc", + "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6752,20 +7005,28 @@ name = "torii-core" version = "0.2.1" dependencies = [ "anyhow", + "async-stream", "async-trait", "camino", "chrono", "dojo-types", "dojo-world", + "futures-channel", + "futures-util", + "hex", + "lazy_static", "log", + "once_cell", "serde", "serde_json", + "slab", "sqlx", "starknet", - "starknet-crypto 0.5.1", + "starknet-crypto 0.6.0", "tokio", "tokio-stream", "tokio-util", + "torii-client", "tracing", ] @@ -6775,36 +7036,62 @@ version = "0.2.1" dependencies = [ "anyhow", "async-graphql", - "async-graphql-poem", + "async-graphql-warp", "async-trait", - "base64 0.21.2", + "base64 0.21.4", "camino", "chrono", + "dojo-test-utils", + "dojo-types", "dojo-world", "indexmap 1.9.3", - "log", - "poem", + "scarb-ui", "serde", "serde_json", + "sozo", "sqlx", "starknet", - "starknet-crypto 0.5.1", + "starknet-crypto 0.6.0", "tokio", "tokio-stream", "tokio-util", + "torii-client", "torii-core", "tracing", "url", + "warp", ] [[package]] name = "torii-grpc" version = "0.2.1" dependencies = [ - "prost", + "anyhow", + "bytes", + "dojo-types", + "futures", + "futures-util", + "hyper", + "parking_lot 0.12.1", + "prost 0.11.9", + "prost 0.12.1", + "rayon 0.9.0", "sqlx", - "tonic", - "tonic-build", + "starknet", + "starknet-crypto 0.6.0", + "thiserror", + "tokio", + "tokio-stream", + "tonic 0.10.1", + "tonic 0.9.2", + "tonic-build 0.10.1", + "tonic-build 0.9.2", + "tonic-web", + "tonic-web-wasm-client", + "tower", + "tracing", + "url", + "warp", ] [[package]] @@ -6813,33 +7100,40 @@ version = "0.2.1" dependencies = [ "anyhow", "async-graphql", - "async-graphql-poem", "async-trait", - "base64 0.21.2", + "base64 0.21.4", "camino", "chrono", "clap", "ctrlc", "dojo-types", "dojo-world", + "either", + "http", + "http-body", + "hyper", "indexmap 1.9.3", - "log", "poem", "scarb", "serde", "serde_json", "sqlx", "starknet", - "starknet-crypto 0.5.1", + "starknet-crypto 0.6.0", "tokio", "tokio-stream", "tokio-util", + "tonic 0.10.1", + "tonic-web", + "torii-client", "torii-core", "torii-graphql", "torii-grpc", + "tower", "tracing", "tracing-subscriber", "url", + "warp", ] [[package]] @@ -6865,12 +7159,12 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "async-compression", - "base64 0.21.2", + "base64 0.21.4", "bitflags 2.4.0", "bytes", "futures-core", @@ -6960,7 +7254,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] @@ -6973,16 +7267,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.1.3" @@ -7020,13 +7304,13 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.19.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ + "base64 0.13.1", "byteorder", "bytes", - "data-encoding", "http", "httparse", "log", @@ -7054,14 +7338,14 @@ checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" @@ -7101,18 +7385,18 @@ dependencies = [ [[package]] name = "unescaper" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995483205de764db1185c9461a000fff73fa4b9ee2bbe4c8b4027a94692700fe" +checksum = "a96a44ae11e25afb520af4534fd7b0bd8cd613e35a78def813b8cf41631fa3c8" dependencies = [ "thiserror", ] [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -7131,9 +7415,9 @@ checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -7152,9 +7436,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -7176,9 +7460,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -7237,9 +7521,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -7255,10 +7539,35 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +name = "warp" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] [[package]] name = "wasi" @@ -7287,7 +7596,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", "wasm-bindgen-shared", ] @@ -7321,7 +7630,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7332,6 +7641,19 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.64" @@ -7344,9 +7666,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", @@ -7361,15 +7683,22 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -7390,9 +7719,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -7409,7 +7738,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.2", + "windows-targets 0.48.5", ] [[package]] @@ -7427,7 +7756,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.2", + "windows-targets 0.48.5", ] [[package]] @@ -7447,17 +7776,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.2", - "windows_aarch64_msvc 0.48.2", - "windows_i686_gnu 0.48.2", - "windows_i686_msvc 0.48.2", - "windows_x86_64_gnu 0.48.2", - "windows_x86_64_gnullvm 0.48.2", - "windows_x86_64_msvc 0.48.2", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -7468,9 +7797,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" @@ -7480,9 +7809,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" @@ -7492,9 +7821,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" @@ -7504,9 +7833,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" @@ -7516,9 +7845,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" @@ -7528,9 +7857,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" @@ -7540,26 +7869,27 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.11" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e461589e194280efaa97236b73623445efa195aa633fd7004f39805707a9d53" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] @@ -7588,9 +7918,9 @@ checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e" [[package]] name = "xxhash-rust" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" +checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" [[package]] name = "yansi" @@ -7615,7 +7945,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.37", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c6e36a0f29..ccaf6c783d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,30 +25,30 @@ repository = "https://github.com/dojoengine/dojo/" version = "0.2.1" [workspace.dependencies] -anyhow = "1.0.66" +anyhow = "1.0.75" async-trait = "0.1.68" blockifier = { git = "https://github.com/starkware-libs/blockifier" } -cairo-lang-casm = "2.1.1" -cairo-lang-compiler = "2.1.1" -cairo-lang-debug = "2.1.1" -cairo-lang-defs = "2.1.1" -cairo-lang-diagnostics = "2.1.1" -cairo-lang-filesystem = "2.1.1" -cairo-lang-formatter = "2.1.1" -cairo-lang-language-server = "2.1.1" -cairo-lang-lowering = "2.1.1" -cairo-lang-parser = "2.1.1" -cairo-lang-plugins = "2.1.1" -cairo-lang-project = "2.1.1" -cairo-lang-semantic = { version = "2.1.1", features = [ "testing" ] } -cairo-lang-sierra = "2.1.1" -cairo-lang-sierra-generator = "2.1.1" -cairo-lang-sierra-to-casm = "2.1.1" -cairo-lang-starknet = "2.1.1" -cairo-lang-syntax = "2.1.1" -cairo-lang-test-runner = "2.1.1" -cairo-lang-test-utils = "2.1.1" -cairo-lang-utils = "2.1.1" +cairo-lang-casm = "2.2.0" +cairo-lang-compiler = "2.2.0" +cairo-lang-debug = "2.2.0" +cairo-lang-defs = "2.2.0" +cairo-lang-diagnostics = "2.2.0" +cairo-lang-filesystem = "2.2.0" +cairo-lang-formatter = "2.2.0" +cairo-lang-language-server = "2.2.0" +cairo-lang-lowering = "2.2.0" +cairo-lang-parser = "2.2.0" +cairo-lang-plugins = "2.2.0" +cairo-lang-project = "2.2.0" +cairo-lang-semantic = { version = "2.2.0", features = [ "testing" ] } +cairo-lang-sierra = "2.2.0" +cairo-lang-sierra-generator = "2.2.0" +cairo-lang-sierra-to-casm = "2.2.0" +cairo-lang-starknet = "2.2.0" +cairo-lang-syntax = "2.2.0" +cairo-lang-test-runner = "2.2.0" +cairo-lang-test-utils = "2.2.0" +cairo-lang-utils = "2.2.0" cairo-vm = "0.8.2" camino = { version = "1.1.2", features = [ "serde1" ] } chrono = { version = "0.4.24", features = [ "serde" ] } @@ -59,31 +59,53 @@ console = "0.15.7" convert_case = "0.6.0" env_logger = "0.10.0" flate2 = "1.0.24" +futures = "0.3.28" indoc = "1.0.7" itertools = "0.10.3" -log = "0.4.17" num-bigint = "0.4" +once_cell = "1.0" +parking_lot = "0.12.1" pretty_assertions = "1.2.1" rayon = "0.9.0" salsa = "0.16.1" -scarb = { git = "https://github.com/software-mansion/scarb", rev = "c07fa61" } +scarb = { git = "https://github.com/software-mansion/scarb", rev = "7adb7fd" } +scarb-ui = { git = "https://github.com/software-mansion/scarb", rev = "7adb7fd" } semver = "1.0.5" serde = { version = "1.0.156", features = [ "derive" ] } serde_json = "1.0" serde_with = "2.3.1" smol_str = { version = "0.2.0", features = [ "serde" ] } -starknet = "0.5.0" -starknet-crypto = "0.5.1" +starknet = "0.6.0" +starknet-crypto = "0.6.0" starknet_api = { git = "https://github.com/starkware-libs/starknet-api", rev = "ecc9b6946ef13003da202838e4124a9ad2efabb0" } +strum = "0.25" +strum_macros = "0.25" test-log = "0.2.11" thiserror = "1.0.32" -tokio = { version = "1.16", features = [ "full" ] } +tokio = { version = "1.32.0", features = [ "full" ] } toml = "0.7.4" -tracing = "0.1" -tracing-subscriber = "0.3.16" +tracing = "0.1.34" +tracing-subscriber = { version = "0.3.16", features = [ "env-filter" ] } +url = "2.4.0" + +# server +hyper = "0.14.27" +warp = "0.3" + +# gRPC +prost = "0.12" +tonic = "0.10" +tonic-build = "0.10" +tonic-web = "0.10.1" + +# WASM-compatible gRPC deps +tonic-web-wasm-client = "0.4.0" +wasm-prost = { version = "0.11.9", package = "prost" } +wasm-tonic = { version = "0.9.2", default-features = false, features = [ "codegen", "gzip", "prost" ], package = "tonic" } +wasm-tonic-build = { version = "0.9.2", default-features = false, features = [ "prost" ], package = "tonic-build" } [patch."https://github.com/starkware-libs/blockifier"] -blockifier = { git = "https://github.com/dojoengine/blockifier", rev = "c794d1b" } +blockifier = { git = "https://github.com/dojoengine/blockifier", rev = "f7df9ba" } [patch.crates-io] cairo-felt = { git = "https://github.com/dojoengine/cairo-rs.git", rev = "262b7eb4b11ab165a2a936a5f914e78aa732d4a2" } diff --git a/README.md b/README.md index 91a3c0da4e..23bc9a5bb0 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,11 @@ Dojo offers a comprehensive suite of onchain game development tools, harnessing See the [installation guide](https://book.dojoengine.org/getting-started/quick-start.html) in the Dojo book. +## 📚 Examples in 30s + +- [Dojo starter react](https://github.com/dojoengine/dojo-starter-react-app) +- [Dojo starter phaser](https://github.com/dojoengine/dojo-starter-phaser) + ## 🗒️ Documentation You can find more detailed documentation in the Dojo Book [here](https://book.dojoengine.org/). diff --git a/crates/dojo-core/Scarb.toml b/crates/dojo-core/Scarb.toml index 871e2092fe..006e4b0783 100644 --- a/crates/dojo-core/Scarb.toml +++ b/crates/dojo-core/Scarb.toml @@ -1,9 +1,9 @@ [package] -cairo-version = "2.1.1" +cairo-version = "2.2.0" description = "The Dojo Core library for autonomous worlds." name = "dojo" version = "0.2.1" [dependencies] dojo_plugin = { git = "https://github.com/dojoengine/dojo" } -starknet = "2.1.1" +starknet = "2.2.0" diff --git a/crates/dojo-core/src/database.cairo b/crates/dojo-core/src/database.cairo index 31dde8ec5d..8a2630d66e 100644 --- a/crates/dojo-core/src/database.cairo +++ b/crates/dojo-core/src/database.cairo @@ -7,6 +7,7 @@ use poseidon::poseidon_hash_span; mod index; #[cfg(test)] mod index_test; +mod schema; mod storage; #[cfg(test)] mod storage_test; @@ -15,23 +16,23 @@ mod utils; mod utils_test; fn get( - class_hash: starknet::ClassHash, table: felt252, key: felt252, offset: u8, length: usize + class_hash: starknet::ClassHash, table: felt252, key: felt252, offset: u8, length: usize, layout: Span ) -> Span { let mut keys = ArrayTrait::new(); keys.append('dojo_storage'); keys.append(table); keys.append(key); - storage::get_many(0, keys.span(), offset, length) + storage::get_many(0, keys.span(), offset, length, layout) } fn set( - class_hash: starknet::ClassHash, table: felt252, key: felt252, offset: u8, value: Span + class_hash: starknet::ClassHash, table: felt252, key: felt252, offset: u8, value: Span, layout: Span ) { let mut keys = ArrayTrait::new(); keys.append('dojo_storage'); keys.append(table); keys.append(key); - storage::set_many(0, keys.span(), offset, value); + storage::set_many(0, keys.span(), offset, value, layout); } fn del(class_hash: starknet::ClassHash, table: felt252, key: felt252) { @@ -41,14 +42,14 @@ fn del(class_hash: starknet::ClassHash, table: felt252, key: felt252) { // returns a tuple of spans, first contains the entity IDs, // second the deserialized entities themselves fn all( - class_hash: starknet::ClassHash, component: felt252, partition: felt252, length: usize + class_hash: starknet::ClassHash, model: felt252, partition: felt252, length: usize, layout: Span ) -> (Span, Span>) { let table = { if partition == 0.into() { - component + model } else { let mut serialized = ArrayTrait::new(); - component.serialize(ref serialized); + model.serialize(ref serialized); partition.serialize(ref serialized); let hash = poseidon_hash_span(serialized.span()); hash.into() @@ -65,7 +66,7 @@ fn all( keys.append('dojo_storage'); keys.append(table); keys.append(*id); - let value: Span = storage::get_many(0, keys.span(), 0_u8, length); + let value: Span = storage::get_many(0, keys.span(), 0_u8, length, layout); entities.append(value); }, Option::None(_) => { diff --git a/crates/dojo-core/src/database/schema.cairo b/crates/dojo-core/src/database/schema.cairo new file mode 100644 index 0000000000..d9bce07f9c --- /dev/null +++ b/crates/dojo-core/src/database/schema.cairo @@ -0,0 +1,219 @@ +#[derive(Copy, Drop, Serde)] +enum Ty { + Simple: felt252, + Struct: Struct, + Enum: EnumMember +} + +#[derive(Copy, Drop, Serde)] +struct Struct { + name: felt252, + attrs: Span, + children: Span> +} + +#[derive(Copy, Drop, Serde)] +struct EnumMember { + name: felt252, + attrs: Span, + values: Span> +} + +#[derive(Copy, Drop, Serde)] +struct Member { + name: felt252, + attrs: Span, + ty: Ty +} + +// Remove once https://github.com/starkware-libs/cairo/issues/4075 is resolved +fn serialize_member(m: @Member) -> Span { + let mut serialized = ArrayTrait::new(); + m.serialize(ref serialized); + serialized.span() +} + +// Remove once https://github.com/starkware-libs/cairo/issues/4075 is resolved +fn serialize_member_type(m: @Ty) -> Span { + let mut serialized = ArrayTrait::new(); + m.serialize(ref serialized); + serialized.span() +} + +trait SchemaIntrospection { + fn size() -> usize; + fn layout(ref layout: Array); + fn ty() -> Ty; +} + +impl SchemaIntrospectionFelt252 of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + // We round down felt252 since it is 251 < felt252 < 252 + layout.append(251); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('felt252') + } +} + +impl SchemaIntrospectionBool of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(1); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('bool') + } +} + +impl SchemaIntrospectionU8 of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(8); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('u8') + } +} + +impl SchemaIntrospectionU16 of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(16); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('u16') + } +} + +impl SchemaIntrospectionU32 of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(32); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('u32') + } +} + +impl SchemaIntrospectionU64 of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(64); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('u64') + } +} + +impl SchemaIntrospectionU128 of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(128); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('u128') + } +} + +impl SchemaIntrospectionU256 of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 2 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(128); + layout.append(128); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('u256') + } +} + +impl SchemaIntrospectionContractAddress of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(251); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('ContractAddress') + } +} + +impl SchemaIntrospectionClassHash of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(251); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Simple('ClassHash') + } +} diff --git a/crates/dojo-core/src/database/storage.cairo b/crates/dojo-core/src/database/storage.cairo index 648ccfbbed..db08f53dc5 100644 --- a/crates/dojo-core/src/database/storage.cairo +++ b/crates/dojo-core/src/database/storage.cairo @@ -4,6 +4,7 @@ use starknet::SyscallResultTrait; use traits::Into; use poseidon::poseidon_hash_span; use serde::Serde; +use dojo::packing::{pack, unpack}; fn get(address_domain: u32, keys: Span) -> felt252 { let base = starknet::storage_base_address_from_felt252(poseidon_hash_span(keys)); @@ -11,9 +12,9 @@ fn get(address_domain: u32, keys: Span) -> felt252 { .unwrap_syscall() } -fn get_many(address_domain: u32, keys: Span, offset: u8, length: usize) -> Span { +fn get_many(address_domain: u32, keys: Span, offset: u8, length: usize, mut layout: Span) -> Span { let base = starknet::storage_base_address_from_felt252(poseidon_hash_span(keys)); - let mut value = ArrayTrait::new(); + let mut packed = ArrayTrait::new(); let mut offset = offset; loop { @@ -21,7 +22,7 @@ fn get_many(address_domain: u32, keys: Span, offset: u8, length: usize) break (); } - value + packed .append( starknet::storage_read_syscall( address_domain, starknet::storage_address_from_base_and_offset(base, offset) @@ -32,7 +33,11 @@ fn get_many(address_domain: u32, keys: Span, offset: u8, length: usize) offset += 1; }; - value.span() + let mut packed = packed.span(); + let mut unpacked = ArrayTrait::new(); + unpack(ref unpacked, ref packed, ref layout); + + unpacked.span() } fn set(address_domain: u32, keys: Span, value: felt252) { @@ -42,15 +47,18 @@ fn set(address_domain: u32, keys: Span, value: felt252) { ); } -fn set_many(address_domain: u32, keys: Span, offset: u8, mut value: Span) { +fn set_many(address_domain: u32, keys: Span, offset: u8, mut unpacked: Span, mut layout: Span) { let base = starknet::storage_base_address_from_felt252(poseidon_hash_span(keys)); + let mut packed = ArrayTrait::new(); + pack(ref packed, ref unpacked, ref layout); + let mut offset = offset; loop { - match value.pop_front() { + match packed.pop_front() { Option::Some(v) => { starknet::storage_write_syscall( - address_domain, starknet::storage_address_from_base_and_offset(base, offset), *v + address_domain, starknet::storage_address_from_base_and_offset(base, offset), v ); offset += 1 }, diff --git a/crates/dojo-core/src/database/storage_test.cairo b/crates/dojo-core/src/database/storage_test.cairo index f782021172..61aab0b8c6 100644 --- a/crates/dojo-core/src/database/storage_test.cairo +++ b/crates/dojo-core/src/database/storage_test.cairo @@ -1,6 +1,7 @@ use array::ArrayTrait; use array::SpanTrait; use traits::Into; +use debug::PrintTrait; use dojo::database::storage; @@ -17,7 +18,76 @@ fn test_storage() { storage::set(0, keys.span(), *values.at(0)); assert(storage::get(0, keys.span()) == *values.at(0), 'value not set'); - storage::set_many(0, keys.span(), 0, values.span()); - let res = storage::get_many(0, keys.span(), 0, 2); + storage::set_many(0, keys.span(), 0, values.span(), array![251, 251].span()); + let res = storage::get_many(0, keys.span(), 0, 2, array![251, 251].span()); assert(*res.at(0) == *values.at(0), 'value not set'); } + +#[test] +#[available_gas(2000000)] +fn test_storage_empty() { + let mut keys = ArrayTrait::new(); + assert(storage::get(0, keys.span()) == 0x0, 'Value should be 0'); + let many = storage::get_many(0, keys.span(), 0, 3, array![251, 251, 251].span()); + assert(*many.at(0) == 0x0, 'Value should be 0'); + assert(*many.at(1) == 0x0, 'Value should be 0'); + assert(*many.at(2) == 0x0, 'Value should be 0'); +} + +#[test] +#[available_gas(100000000)] +fn test_storage_get_many_length() { + let mut keys = ArrayTrait::new(); + let mut layout = array![]; + let mut i = 0_usize; + loop { + if i >= 30 { + break; + }; + + layout.append(251); + assert(storage::get_many(0, keys.span(), 0, i, layout.span()).len() == i, 'Values should be equal!'); + i += 1; + }; +} + +#[test] +#[available_gas(2000000)] +fn test_storage_set_many() { + let mut keys = ArrayTrait::new(); + keys.append(0x966); + + let mut values = ArrayTrait::new(); + values.append(0x1); + values.append(0x2); + values.append(0x3); + values.append(0x4); + + storage::set_many(0, keys.span(), 0, values.span(), array![251, 251, 251, 251].span()); + let many = storage::get_many(0, keys.span(), 0, 4, array![251, 251, 251, 251].span()); + assert(many.at(0) == values.at(0), 'Value at 0 not equal!'); + assert(many.at(1) == values.at(1), 'Value at 1 not equal!'); + assert(many.at(2) == values.at(2), 'Value at 2 not equal!'); + assert(many.at(3) == values.at(3), 'Value at 3 not equal!'); +} + +#[test] +#[available_gas(20000000)] +fn test_storage_set_many_with_offset() { + let mut keys = ArrayTrait::new(); + keys.append(0x1364); + + let mut values = ArrayTrait::new(); + values.append(0x1); + values.append(0x2); + values.append(0x3); + values.append(0x4); + + storage::set_many(0, keys.span(), 1, values.span(), array![251, 251, 251, 251].span()); + let many = storage::get_many(0, keys.span(), 0, 5, array![251, 251, 251, 251, 251].span()); + assert(*many.at(0) == 0x0, 'Value at 0 not equal!'); + assert(many.at(1) == values.at(0), 'Value at 1 not equal!'); + assert(many.at(2) == values.at(1), 'Value at 2 not equal!'); + assert(many.at(3) == values.at(2), 'Value at 3 not equal!'); + assert(many.at(4) == values.at(3), 'Value at 4 not equal!'); +} diff --git a/crates/dojo-core/src/database/utils.cairo b/crates/dojo-core/src/database/utils.cairo index 3677623310..62ae3ff531 100644 --- a/crates/dojo-core/src/database/utils.cairo +++ b/crates/dojo-core/src/database/utils.cairo @@ -15,7 +15,7 @@ const OFFSET: felt252 = 0x10000000000000000000000000000000000; // * `entities` is a list of lists of deserialized entities; each list of entities // is of the same entity type in the order of IDs from `ids // -// to illustrate, consider we have two entity types (components), Place and Owner +// to illustrate, consider we have two entity types (models), Place and Owner // `ids` are [[4, 2, 3], [3, 4, 5]] // `entities` are [[P4, P2, P3], [O3, O4, O5]] // where P4 is a deserialized (i.e. a Span) Place entity with ID 4, diff --git a/crates/dojo-core/src/database_test.cairo b/crates/dojo-core/src/database_test.cairo new file mode 100644 index 0000000000..e329ef0f45 --- /dev/null +++ b/crates/dojo-core/src/database_test.cairo @@ -0,0 +1,136 @@ +use core::result::ResultTrait; +use array::ArrayTrait; +use option::OptionTrait; +use serde::Serde; +use array::SpanTrait; +use traits::{Into, TryInto}; + +use starknet::syscalls::deploy_syscall; +use starknet::class_hash::{Felt252TryIntoClassHash, ClassHash}; +use dojo::world::{IWorldDispatcher}; +use dojo::executor::executor; +use dojo::database::{get, set, del, all}; + +#[test] +#[available_gas(1000000)] +fn test_database_basic() { + let mut values = ArrayTrait::new(); + values.append('database_test'); + values.append('42'); + + let class_hash: starknet::ClassHash = executor::TEST_CLASS_HASH.try_into().unwrap(); + set(class_hash, 'table', 'key', 0, values.span(), array![251, 251].span()); + let res = get(class_hash, 'table', 'key', 0, values.len(), array![251, 251].span()); + + assert(res.at(0) == values.at(0), 'Value at 0 not equal!'); + assert(res.at(1) == values.at(1), 'Value at 0 not equal!'); + assert(res.len() == values.len(), 'Lengths not equal'); +} + +#[test] +#[available_gas(1500000)] +fn test_database_different_tables() { + let mut values = ArrayTrait::new(); + values.append(0x1); + values.append(0x2); + + let mut other = ArrayTrait::new(); + other.append(0x3); + other.append(0x4); + + let class_hash: starknet::ClassHash = executor::TEST_CLASS_HASH.try_into().unwrap(); + set(class_hash, 'first', 'key', 0, values.span(), array![251, 251].span()); + set(class_hash, 'second', 'key', 0, other.span(), array![251, 251].span()); + let res = get(class_hash, 'first', 'key', 0, values.len(), array![251, 251].span()); + let other_res = get(class_hash, 'second', 'key', 0, other.len(), array![251, 251].span()); + + assert(res.len() == values.len(), 'Lengths not equal'); + assert(res.at(0) == values.at(0), 'Values different at `first`!'); + assert(other_res.at(0) == other_res.at(0), 'Values different at `second`!'); + assert(other_res.at(0) != res.at(0), 'Values the same for different!'); +} + +#[test] +#[available_gas(1500000)] +fn test_database_different_keys() { + let mut values = ArrayTrait::new(); + values.append(0x1); + values.append(0x2); + + let mut other = ArrayTrait::new(); + other.append(0x3); + other.append(0x4); + + let class_hash: starknet::ClassHash = executor::TEST_CLASS_HASH.try_into().unwrap(); + set(class_hash, 'table', 'key', 0, values.span(), array![251, 251].span()); + set(class_hash, 'table', 'other', 0, other.span(), array![251, 251].span()); + let res = get(class_hash, 'table', 'key', 0, values.len(), array![251, 251].span()); + let other_res = get(class_hash, 'table', 'other', 0, other.len(), array![251, 251].span()); + + assert(res.len() == values.len(), 'Lengths not equal'); + assert(res.at(0) == values.at(0), 'Values different at `key`!'); + assert(other_res.at(0) == other_res.at(0), 'Values different at `other`!'); + assert(other_res.at(0) != res.at(0), 'Values the same for different!'); +} + +#[test] +#[available_gas(10000000)] +fn test_database_pagination() { + let mut values = ArrayTrait::new(); + values.append(0x1); + values.append(0x2); + values.append(0x3); + values.append(0x4); + values.append(0x5); + + let class_hash: starknet::ClassHash = executor::TEST_CLASS_HASH.try_into().unwrap(); + set(class_hash, 'table', 'key', 1, values.span(), array![251, 251, 251, 251, 251].span()); + let first_res = get(class_hash, 'table', 'key', 1, 3, array![251, 251, 251].span()); + let second_res = get(class_hash, 'table', 'key', 3, 5, array![251, 251, 251, 251, 251].span()); + let third_res = get( + class_hash, 'table', 'key', 5, 7, array![251, 251, 251, 251, 251, 251, 251].span() + ); + + assert(*first_res.at(0) == *values.at(0), 'Values different at index 0!'); + assert(*first_res.at(1) == *values.at(1), 'Values different at index 1!'); + assert(*second_res.at(0) == *values.at(2), 'Values different at index 2!'); + assert(*second_res.at(1) == *values.at(3), 'Values different at index 3!'); + assert(*third_res.at(0) == *values.at(4), 'Values different at index 4!'); + assert(*third_res.at(1) == 0x0, 'Value not empty at index 5!'); +} + +#[test] +#[available_gas(10000000)] +fn test_database_del() { + let mut values = ArrayTrait::new(); + values.append(0x42); + + let class_hash: starknet::ClassHash = executor::TEST_CLASS_HASH.try_into().unwrap(); + set(class_hash, 'table', 'key', 0, values.span(), array![251].span()); + + let before = get(class_hash, 'table', 'key', 0, values.len(), array![251].span()); + assert(*before.at(0) == *values.at(0), 'Values different at index 0!'); + + del(class_hash, 'table', 'key'); + let after = get(class_hash, 'table', 'key', 0, 0, array![].span()); + assert(after.len() == 0, 'Non empty after deletion!'); +} + +#[test] +#[available_gas(10000000)] +fn test_database_all() { + let mut even = ArrayTrait::new(); + even.append(0x2); + even.append(0x4); + + let mut odd = ArrayTrait::new(); + odd.append(0x1); + odd.append(0x3); + + let class_hash: starknet::ClassHash = executor::TEST_CLASS_HASH.try_into().unwrap(); + set(class_hash, 'table', 'even', 0, even.span(), array![251, 251].span()); + set(class_hash, 'table', 'odd', 0, odd.span(), array![251, 251].span()); + + let base = starknet::storage_base_address_from_felt252('table'); + let (keys, values) = all(class_hash, 'table', 0, 2, array![251, 251].span()); +} diff --git a/crates/dojo-core/src/executor.cairo b/crates/dojo-core/src/executor.cairo index 65cade5c3d..897aa228e4 100644 --- a/crates/dojo-core/src/executor.cairo +++ b/crates/dojo-core/src/executor.cairo @@ -1,10 +1,7 @@ use starknet::ClassHash; -use dojo::world::Context; - #[starknet::interface] trait IExecutor { - fn execute(self: @T, class_hash: ClassHash, calldata: Span) -> Span; fn call( self: @T, class_hash: ClassHash, entrypoint: felt252, calldata: Span ) -> Span; @@ -14,7 +11,7 @@ trait IExecutor { mod executor { use array::{ArrayTrait, SpanTrait}; use option::OptionTrait; - use starknet::ClassHash; + use starknet::{ClassHash, SyscallResultTrait, SyscallResultTraitImpl}; use super::IExecutor; @@ -26,23 +23,6 @@ mod executor { #[external(v0)] impl Executor of IExecutor { - /// Executes a System by calling its execute entrypoint. - /// - /// # Arguments - /// - /// * `class_hash` - Class Hash of the System. - /// * `calldata` - Calldata to pass to the System. - /// - /// # Returns - /// - /// The return value of the System's execute entrypoint. - fn execute( - self: @ContractState, class_hash: ClassHash, calldata: Span - ) -> Span { - starknet::syscalls::library_call_syscall(class_hash, EXECUTE_ENTRYPOINT, calldata) - .unwrap_syscall() - } - /// Call the provided `entrypoint` method on the given `class_hash`. /// /// # Arguments diff --git a/crates/dojo-core/src/executor_test.cairo b/crates/dojo-core/src/executor_test.cairo index 94ae06f1b7..cfb0f5f4fe 100644 --- a/crates/dojo-core/src/executor_test.cairo +++ b/crates/dojo-core/src/executor_test.cairo @@ -8,9 +8,9 @@ use traits::TryInto; use starknet::syscalls::deploy_syscall; use starknet::class_hash::Felt252TryIntoClassHash; use dojo::executor::{executor, IExecutorDispatcher, IExecutorDispatcherTrait}; -use dojo::world::{Context, IWorldDispatcher}; +use dojo::world::{IWorldDispatcher}; -#[derive(Component, Copy, Drop, Serde, SerdeLen)] +#[derive(Model, Copy, Drop, Serde)] struct Foo { #[key] id: felt252, @@ -18,15 +18,26 @@ struct Foo { b: u128, } -#[system] -mod Bar { - use super::Foo; +#[starknet::contract] +mod bar { + use super::{Foo}; - fn execute(foo: Foo) -> Foo { + #[storage] + struct Storage {} + + #[external(v0)] + fn name(self: @ContractState) -> felt252 { + 'bar' + } + + #[external(v0)] + fn execute(self: @ContractState, foo: Foo) -> Foo { foo } } +const NAME_ENTRYPOINT: felt252 = 0x0361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60; + #[test] #[available_gas(40000000)] fn test_executor() { @@ -38,21 +49,10 @@ fn test_executor() { let executor = IExecutorDispatcher { contract_address: executor_address }; - let mut system_calldata = ArrayTrait::new(); - system_calldata.append(1); - system_calldata.append(42); - system_calldata.append(53); - - let ctx = Context { - world: IWorldDispatcher { - contract_address: starknet::contract_address_const::<0x1337>() - }, - origin: starknet::contract_address_const::<0x1337>(), - system: 'Bar', - system_class_hash: Bar::TEST_CLASS_HASH.try_into().unwrap(), - }; + starknet::testing::set_contract_address(starknet::contract_address_const::<0x1337>()); - ctx.serialize(ref system_calldata); + let res = *executor + .call(bar::TEST_CLASS_HASH.try_into().unwrap(), NAME_ENTRYPOINT, array![].span())[0]; - let res = executor.execute(ctx.system_class_hash, system_calldata.span()); + assert(res == 'bar', 'executor call incorrect') } diff --git a/crates/dojo-core/src/lib.cairo b/crates/dojo-core/src/lib.cairo index ee4c12ce6b..450842abc6 100644 --- a/crates/dojo-core/src/lib.cairo +++ b/crates/dojo-core/src/lib.cairo @@ -1,16 +1,16 @@ mod database; +#[cfg(test)] +mod database_test; mod executor; #[cfg(test)] mod executor_test; -mod serde; -use serde::SerdeLen; -mod traits; +mod model; +mod packing; +#[cfg(test)] +mod packing_test; mod world; #[cfg(test)] mod world_test; -mod world_factory; -#[cfg(test)] -mod world_factory_test; #[cfg(test)] mod test_utils; diff --git a/crates/dojo-core/src/model.cairo b/crates/dojo-core/src/model.cairo new file mode 100644 index 0000000000..2547a9275a --- /dev/null +++ b/crates/dojo-core/src/model.cairo @@ -0,0 +1,14 @@ +trait Model { + fn name(self: @T) -> felt252; + fn keys(self: @T) -> Span; + fn values(self: @T) -> Span; + fn layout(self: @T) -> Span; + fn packed_size(self: @T) -> usize; +} + +#[starknet::interface] +trait IModel { + fn name(self: @T) -> felt252; + fn layout(self: @T) -> Span; + fn schema(self: @T) -> Span; +} diff --git a/crates/dojo-core/src/packing.cairo b/crates/dojo-core/src/packing.cairo new file mode 100644 index 0000000000..2d7bff76a6 --- /dev/null +++ b/crates/dojo-core/src/packing.cairo @@ -0,0 +1,157 @@ +use starknet::{ClassHash, ContractAddress}; +use array::{ArrayTrait, SpanTrait}; +use traits::{Into, TryInto}; +use integer::{U256BitAnd, U256BitOr, U256BitXor, upcast, downcast, BoundedInt}; +use option::OptionTrait; + +fn pack(ref packed: Array, ref unpacked: Span, ref layout: Span) { + assert(unpacked.len() == layout.len(), 'mismatched input lens'); + let mut packing: felt252 = 0x0; + let mut offset: u8 = 0x0; + loop { + match unpacked.pop_front() { + Option::Some(item) => { + pack_inner(item, *layout.pop_front().unwrap(), ref packing, ref offset, ref packed); + }, + Option::None(_) => { + break; + } + }; + }; + packed.append(packing); +} + +fn calculate_packed_size(ref layout: Span) -> usize { + let mut size = 1; + let mut partial = 0_usize; + + loop { + match layout.pop_front() { + Option::Some(item) => { + let item_size: usize = (*item).into(); + partial += item_size; + if (partial > 251) { + size += 1; + partial = item_size; + } + }, + Option::None(_) => { + break; + } + }; + }; + + size +} + +fn unpack(ref unpacked: Array, ref packed: Span, ref layout: Span) { + let mut unpacking: felt252 = 0x0; + let mut offset: u8 = 251; + loop { + match layout.pop_front() { + Option::Some(s) => { + match unpack_inner(*s, ref packed, ref unpacking, ref offset) { + Option::Some(u) => { + unpacked.append(u); + }, + Option::None(_) => { + // TODO: Raise error + break; + } + } + }, + Option::None(_) => { + break; + } + }; + } +} + +/// Pack the proposal fields into a single felt252. +fn pack_inner( + self: @felt252, + size: u8, + ref packing: felt252, + ref packing_offset: u8, + ref packed: Array +) { + // Cannot use all 252 bits because some bit arrangements (eg. 11111...11111) are not valid felt252 values. + // Thus only 251 bits are used. ^-252 times-^ + // One could optimize by some conditional alligment mechanism, but it would be an at most 1/252 space-wise improvement. + let remaining_bits: u8 = (251 - packing_offset).into(); + + // If we have less remaining bits than the current item size, + // Finalize the current `packing`felt and move to the next felt. + if remaining_bits < size { + packed.append(packing); + packing = *self; + packing_offset = size; + return; + } + + // Easier to work on u256 rather than felt252. + let self_256: u256 = (*self).into(); + + // Pack item into the `packing` felt. + let mut packing_256: u256 = packing.into(); + packing_256 = packing_256 | shl(self_256, packing_offset); + packing = packing_256.try_into().unwrap(); + packing_offset = packing_offset + size; +} + +fn unpack_inner( + size: u8, ref packed: Span, ref unpacking: felt252, ref unpacking_offset: u8 +) -> Option { + let remaining_bits: u8 = (251 - unpacking_offset).into(); + + // If less remaining bits than size, we move to the next + // felt for unpacking. + if remaining_bits < size { + match packed.pop_front() { + Option::Some(val) => { + unpacking = *val; + unpacking_offset = size; + + // If we are unpacking a full felt. + if (size == 251) { + return Option::Some(unpacking); + } + + let val_256: u256 = (*val).into(); + let result = val_256 & (shl(1, size) - 1); + return result.try_into(); + }, + Option::None(()) => { + return Option::None(()); + }, + } + } + + let mut unpacking_256: u256 = unpacking.into(); + let result = (shl(1, size) - 1) & shr(unpacking_256, unpacking_offset); + unpacking_offset = unpacking_offset + size; + return result.try_into(); +} + +fn fpow(x: u256, n: u8) -> u256 { + let y = x; + if n == 0 { + return 1; + } + if n == 1 { + return x; + } + let double = fpow(y * x, n / 2); + if (n % 2) == 1 { + return x * double; + } + return double; +} + +fn shl(x: u256, n: u8) -> u256 { + x * fpow(2, n) +} + +fn shr(x: u256, n: u8) -> u256 { + x / fpow(2, n) +} \ No newline at end of file diff --git a/crates/dojo-core/src/packing_test.cairo b/crates/dojo-core/src/packing_test.cairo new file mode 100644 index 0000000000..4935e9bc84 --- /dev/null +++ b/crates/dojo-core/src/packing_test.cairo @@ -0,0 +1,342 @@ +use array::{ArrayTrait, SpanTrait}; +use starknet::{ClassHash, ContractAddress, Felt252TryIntoContractAddress, Felt252TryIntoClassHash}; +use dojo::packing::{shl, shr, fpow, pack, unpack, pack_inner, unpack_inner, calculate_packed_size}; +use integer::U256BitAnd; +use option::OptionTrait; +use debug::PrintTrait; +use traits::{Into, TryInto}; +use dojo::database::schema::SchemaIntrospection; + +#[test] +#[available_gas(9000000)] +fn test_bit_fpow() { + assert(fpow(2, 250) == 1809251394333065553493296640760748560207343510400633813116524750123642650624_u256, '') +} + +#[test] +#[available_gas(9000000)] +fn test_bit_shift() { + assert(1 == shl(1, 0), 'left == right'); + assert(1 == shr(1, 0), 'left == right'); + + assert(16 == shl(1, 4), 'left == right'); + assert(1 == shr(16, 4), 'left == right'); + + assert(shr(shl(1, 251), 251) == 1, 'left == right') +} + +#[test] +#[available_gas(9000000)] +fn test_pack_unpack_single() { + let mut packed = array::ArrayTrait::new(); + let mut packing: felt252 = 0; + let mut offset = 0; + pack_inner(@18, 251, ref packing, ref offset, ref packed); + packed.append(packing); + + assert(*packed.at(0) == 18, 'Packing single value'); + + let mut unpacking: felt252 = packed.pop_front().unwrap(); + let mut un_offset = 0; + let mut packed_span = packed.span(); + + let result = unpack_inner(251, ref packed_span, ref unpacking, ref un_offset).unwrap(); + assert(result == 18, 'Unpacked equals packed'); +} + +#[test] +#[available_gas(9000000)] +fn test_pack_unpack_felt252_u128() { + let mut packed = array::ArrayTrait::new(); + let mut packing: felt252 = 0; + let mut offset = 0; + pack_inner(@1337, 128, ref packing, ref offset, ref packed); + pack_inner(@420, 252, ref packing, ref offset, ref packed); + packed.append(packing); + + let mut unpacking: felt252 = packed.pop_front().unwrap(); + let mut un_offset = 0; + let mut packed_span = packed.span(); + + assert( + unpack_inner(128, ref packed_span, ref unpacking, ref un_offset).unwrap() + == 1337, + 'Types u8' + ); + assert( + unpack_inner(252, ref packed_span, ref unpacking, ref un_offset).unwrap() + == 420, + 'Types u8' + ); +} + +#[test] +#[available_gas(100000000)] +fn test_pack_multiple() { + let mut packed = array::ArrayTrait::new(); + let mut packing: felt252 = 0; + let mut offset = 0; + + let mut i: u32 = 0; + loop { + if i >= 20 { + break; + } + pack_inner(@i.into(), 32, ref packing, ref offset, ref packed); + i += 1; + }; + packed.append(packing); + + assert( + *packed.at(0) == 0x6000000050000000400000003000000020000000100000000, + 'Packed multiple 0' + ); + assert( + *packed.at(1) == 0xd0000000c0000000b0000000a000000090000000800000007, + 'Packed multiple 1' + ); + assert( + *packed.at(2) == 0x130000001200000011000000100000000f0000000e, + 'Packed multiple 2' + ); +} + +#[test] +#[available_gas(500000000)] +fn test_pack_unpack_multiple() { + let mut packed = array::ArrayTrait::new(); + let mut packing: felt252 = 0; + let mut offset = 0; + + let mut i: u8 = 0; + loop { + if i >= 40 { + break; + } + let mut j: u32 = i.into(); + j = (j + 3) * j; + + pack_inner(@i.into(), 8, ref packing, ref offset, ref packed); + pack_inner(@j.into(), 32, ref packing, ref offset, ref packed); + + i += 1; + }; + packed.append(packing); + + let mut unpacking: felt252 = packed.pop_front().unwrap(); + let mut un_offset = 0; + let mut packed_span = packed.span(); + + i = 0; + loop { + if i >= 40 { + break; + } + let result_i = unpack_inner(8, ref packed_span, ref unpacking, ref un_offset).unwrap(); + let result_j = unpack_inner(32, ref packed_span, ref unpacking, ref un_offset).unwrap(); + + let mut j: u32 = i.into(); + j = (j + 3) * j; + + assert(result_i.try_into().unwrap() == i, 'Unpacked equals packed'); + assert(result_j.try_into().unwrap() == j, 'Unpacked equals packed'); + i += 1; + }; +} + +#[test] +#[available_gas(500000000)] +fn test_pack_unpack_types() { + let mut packed = array::ArrayTrait::new(); + let mut packing: felt252 = 0; + let mut offset = 0; + + let mut i: u8 = 0; + pack_inner(@3, 8, ref packing, ref offset, ref packed); + pack_inner(@14, 16, ref packing, ref offset, ref packed); + pack_inner(@59, 32, ref packing, ref offset, ref packed); + pack_inner(@26, 64, ref packing, ref offset, ref packed); + pack_inner(@53, 128, ref packing, ref offset, ref packed); + pack_inner(@58, 251, ref packing, ref offset, ref packed); + pack_inner(@false.into(), 1, ref packing, ref offset, ref packed); + + let contract_address = Felt252TryIntoContractAddress::try_into(3).unwrap(); + pack_inner(@contract_address.into(), 251, ref packing, ref offset, ref packed); + let class_hash = Felt252TryIntoClassHash::try_into(1337).unwrap(); + pack_inner(@class_hash.into(), 251, ref packing, ref offset, ref packed); + + packed.append(packing); + + let mut unpacking: felt252 = packed.pop_front().unwrap(); + let mut un_offset = 0; + let mut packed_span = packed.span(); + + assert( + unpack_inner(8, ref packed_span, ref unpacking, ref un_offset).unwrap().try_into().unwrap() + == 3_u8, + 'Types u8' + ); + assert( + unpack_inner(16, ref packed_span, ref unpacking, ref un_offset).unwrap().try_into().unwrap() + == 14_u16, + 'Types u16' + ); + assert( + unpack_inner(32, ref packed_span, ref unpacking, ref un_offset).unwrap().try_into().unwrap() + == 59_u32, + 'Types u32' + ); + assert( + unpack_inner(64, ref packed_span, ref unpacking, ref un_offset).unwrap().try_into().unwrap() + == 26_u64, + 'Types u64' + ); + assert( + unpack_inner(128, ref packed_span, ref unpacking, ref un_offset).unwrap().try_into().unwrap() + == 53_u128, + 'Types u128' + ); + assert( + unpack_inner(251, ref packed_span, ref unpacking, ref un_offset).unwrap() + == 58_felt252, + 'Types felt252' + ); + assert( + unpack_inner(1, ref packed_span, ref unpacking, ref un_offset).unwrap() + == false.into(), + 'Types bool' + ); + assert( + unpack_inner(251, ref packed_span, ref unpacking, ref un_offset).unwrap().try_into().unwrap() + == contract_address, + 'Types ContractAddress' + ); + assert( + unpack_inner(251, ref packed_span, ref unpacking, ref un_offset).unwrap().try_into().unwrap() + == class_hash, + 'Types ClassHash' + ); +} + +#[test] +#[available_gas(9000000)] +fn test_inner_pack_unpack_u256_single() { + let input: u256 = 2000; + let mut packed = array::ArrayTrait::new(); + let mut packing: felt252 = 0; + let mut offset = 0; + pack_inner(@input.low.into(), 128, ref packing, ref offset, ref packed); + pack_inner(@input.high.into(), 128, ref packing, ref offset, ref packed); + packed.append(packing); + + assert(*packed.at(0) == 2000, 'Packing low value'); + assert(*packed.at(1) == 0, 'Packing high value'); + + let mut unpacking: felt252 = packed.pop_front().unwrap(); + let mut un_offset = 0; + let mut packed_span = packed.span(); + + let low = unpack_inner(128, ref packed_span, ref unpacking, ref un_offset).unwrap(); + let high = unpack_inner(128, ref packed_span, ref unpacking, ref un_offset).unwrap(); + assert(u256 { low: low.try_into().unwrap(), high: high.try_into().unwrap() } == input, 'Unpacked equals packed'); +} + +#[test] +#[available_gas(9000000)] +fn test_pack_unpack_u256_single() { + let input: u256 = 2000; + let mut unpacked = ArrayTrait::new(); + input.serialize(ref unpacked); + let mut layout = ArrayTrait::new(); + SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + + let mut unpacked_span = unpacked.span(); + + let mut packed = array::ArrayTrait::new(); + pack(ref packed, ref unpacked_span, ref layout_span); + + let mut layout = ArrayTrait::new(); + SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + + let mut unpacked = array::ArrayTrait::new(); + let mut packed_span = packed.span(); + unpack(ref unpacked, ref packed_span, ref layout_span); + let mut unpacked_span = unpacked.span(); + let output = serde::Serde::::deserialize(ref unpacked_span).unwrap(); + assert(input == output, 'invalid output'); +} + +#[test] +#[available_gas(9000000)] +fn test_pack_unpack_max_felt252() { + let MAX: felt252 = 3618502788666131213697322783095070105623107215331596699973092056135872020480; + let mut packed = array::ArrayTrait::new(); + let mut packing: felt252 = 0; + let mut offset = 0; + pack_inner(@MAX, 251, ref packing, ref offset, ref packed); + packed.append(packing); + + let mut unpacking: felt252 = 0; + let mut offset = 251; + let mut packed_span = packed.span(); + + let got = unpack_inner(251, ref packed_span, ref unpacking, ref offset).unwrap(); + assert(got == MAX, 'Types MAX'); +} + +#[test] +#[available_gas(9000000)] +fn test_pack_unpack_felt252_single() { + let input = 2000; + let mut unpacked = ArrayTrait::new(); + input.serialize(ref unpacked); + let mut layout = ArrayTrait::new(); + SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + + let mut unpacked_span = unpacked.span(); + + let mut packed = array::ArrayTrait::new(); + pack(ref packed, ref unpacked_span, ref layout_span); + + let mut layout = ArrayTrait::new(); + SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + + let mut unpacked = array::ArrayTrait::new(); + let mut packed_span = packed.span(); + unpack(ref unpacked, ref packed_span, ref layout_span); + let mut unpacked_span = unpacked.span(); + let output = serde::Serde::::deserialize(ref unpacked_span).unwrap(); + assert(input == output, 'invalid output'); +} + +#[test] +#[available_gas(9000000)] +fn test_calculate_packed_size() { + let mut layout = array![128, 32].span(); + let got = calculate_packed_size(ref layout); + assert(got == 1, 'invalid length for [128, 32]'); + + let mut layout = array![128, 128].span(); + let got = calculate_packed_size(ref layout); + assert(got == 2, 'invalid length for [128, 128]'); + + let mut layout = array![251, 251].span(); + let got = calculate_packed_size(ref layout); + assert(got == 2, 'invalid length for [251, 251]'); + + let mut layout = array![251].span(); + let got = calculate_packed_size(ref layout); + assert(got == 1, 'invalid length for [251]'); + + let mut layout = array![32, 64, 128, 27].span(); + let got = calculate_packed_size(ref layout); + assert(got == 1, 'invalid length'); + + let mut layout = array![32, 64, 128, 28].span(); + let got = calculate_packed_size(ref layout); + assert(got == 2, 'invalid length'); +} diff --git a/crates/dojo-core/src/serde.cairo b/crates/dojo-core/src/serde.cairo deleted file mode 100644 index c81d8eb116..0000000000 --- a/crates/dojo-core/src/serde.cairo +++ /dev/null @@ -1,73 +0,0 @@ -trait SerdeLen { - fn len() -> usize; -} - -impl SerdeLenFelt252 of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenBool of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenU8 of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenU16 of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenU32 of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenU64 of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenU128 of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenU256 of SerdeLen { - #[inline(always)] - fn len() -> usize { - 2 - } -} - -impl SerdeLenContractAddress of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} - -impl SerdeLenClassHash of SerdeLen { - #[inline(always)] - fn len() -> usize { - 1 - } -} diff --git a/crates/dojo-core/src/test_utils.cairo b/crates/dojo-core/src/test_utils.cairo index e70afaa290..eb3db0c220 100644 --- a/crates/dojo-core/src/test_utils.cairo +++ b/crates/dojo-core/src/test_utils.cairo @@ -7,17 +7,47 @@ use traits::TryInto; use option::OptionTrait; use core::{result::ResultTrait, traits::Into}; + use dojo::executor::executor; use dojo::world::{world, IWorldDispatcher, IWorldDispatcherTrait}; -fn spawn_test_world(components: Array, systems: Array) -> IWorldDispatcher { +/// Deploy classhash with calldata for constructor +/// +/// # Arguments +/// +/// * `class_hash` - Class to deploy +/// * `calldata` - calldata for constructor +/// +/// # Returns +/// * address of contract deployed +fn deploy_contract(class_hash: felt252, calldata: Span) -> ContractAddress { + let (system_contract, _) = starknet::deploy_syscall( + class_hash.try_into().unwrap(), 0, calldata, false + ) + .unwrap(); + system_contract +} + +/// Deploy classhash and passes in world address to constructor +/// +/// # Arguments +/// +/// * `class_hash` - Class to deploy +/// * `world` - World dispatcher to pass as world address +/// +/// # Returns +/// * address of contract deployed +fn deploy_with_world_address(class_hash: felt252, world: IWorldDispatcher) -> ContractAddress { + deploy_contract(class_hash, array![world.contract_address.into()].span()) +} + +fn spawn_test_world(models: Array) -> IWorldDispatcher { // deploy executor let constructor_calldata = array::ArrayTrait::new(); let (executor_address, _) = deploy_syscall( executor::TEST_CLASS_HASH.try_into().unwrap(), 0, constructor_calldata.span(), false ) .unwrap(); - // deploy world let mut world_constructor_calldata = array::ArrayTrait::new(); world_constructor_calldata.append(executor_address.into()); @@ -27,23 +57,13 @@ fn spawn_test_world(components: Array, systems: Array) -> IWor .unwrap(); let world = IWorldDispatcher { contract_address: world_address }; - // register components - let mut index = 0; - loop { - if index == components.len() { - break (); - } - world.register_component((*components[index]).try_into().unwrap()); - index += 1; - }; - - // register systems + // register models let mut index = 0; loop { - if index == systems.len() { + if index == models.len() { break (); } - world.register_system((*systems[index]).try_into().unwrap()); + world.register_model((*models[index]).try_into().unwrap()); index += 1; }; diff --git a/crates/dojo-core/src/traits.cairo b/crates/dojo-core/src/traits.cairo deleted file mode 100644 index 7891a87217..0000000000 --- a/crates/dojo-core/src/traits.cairo +++ /dev/null @@ -1,10 +0,0 @@ -trait Component { - fn name(self: @T) -> felt252; - fn keys(self: @T) -> Span; - fn values(self: @T) -> Span; -} - -#[starknet::interface] -trait INamed { - fn name(self: @T) -> felt252; -} diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index 750c39c050..f95b70ba99 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -2,45 +2,41 @@ use starknet::{ContractAddress, ClassHash, StorageBaseAddress, SyscallResult}; use traits::{Into, TryInto}; use option::OptionTrait; -#[derive(Copy, Drop, Serde)] -struct Context { - world: IWorldDispatcher, // Dispatcher to the world contract - origin: ContractAddress, // Address of the origin - system: felt252, // Name of the calling system - system_class_hash: ClassHash, // Class hash of the calling system -} - #[starknet::interface] trait IWorld { - fn component(self: @T, name: felt252) -> ClassHash; - fn register_component(ref self: T, class_hash: ClassHash); - fn system(self: @T, name: felt252) -> ClassHash; - fn register_system(ref self: T, class_hash: ClassHash); + fn model(self: @T, name: felt252) -> ClassHash; + fn register_model(ref self: T, class_hash: ClassHash); fn uuid(ref self: T) -> usize; fn emit(self: @T, keys: Array, values: Span); - fn execute(ref self: T, system: felt252, calldata: Array) -> Span; fn entity( - self: @T, component: felt252, keys: Span, offset: u8, length: usize + self: @T, + model: felt252, + keys: Span, + offset: u8, + length: usize, + layout: Span ) -> Span; fn set_entity( - ref self: T, component: felt252, keys: Span, offset: u8, value: Span + ref self: T, + model: felt252, + keys: Span, + offset: u8, + values: Span, + layout: Span ); fn entities( - self: @T, component: felt252, index: felt252, length: usize + self: @T, model: felt252, index: felt252, length: usize, layout: Span ) -> (Span, Span>); fn set_executor(ref self: T, contract_address: ContractAddress); fn executor(self: @T) -> ContractAddress; - fn delete_entity(ref self: T, component: felt252, keys: Span); - fn origin(self: @T) -> ContractAddress; - fn caller_system(self: @T) -> felt252; - + fn delete_entity(ref self: T, model: felt252, keys: Span); fn is_owner(self: @T, address: ContractAddress, target: felt252) -> bool; fn grant_owner(ref self: T, address: ContractAddress, target: felt252); fn revoke_owner(ref self: T, address: ContractAddress, target: felt252); - fn is_writer(self: @T, component: felt252, system: felt252) -> bool; - fn grant_writer(ref self: T, component: felt252, system: felt252); - fn revoke_writer(ref self: T, component: felt252, system: felt252); + fn is_writer(self: @T, model: felt252, system: ContractAddress) -> bool; + fn grant_writer(ref self: T, model: felt252, system: ContractAddress); + fn revoke_writer(ref self: T, model: felt252, system: ContractAddress); } #[starknet::contract] @@ -53,25 +49,23 @@ mod world { use starknet::{ get_caller_address, get_contract_address, get_tx_info, contract_address::ContractAddressIntoFelt252, ClassHash, Zeroable, ContractAddress, - syscalls::emit_event_syscall + syscalls::emit_event_syscall, SyscallResultTrait, SyscallResultTraitImpl }; use dojo::database; use dojo::executor::{IExecutorDispatcher, IExecutorDispatcherTrait}; - use dojo::traits::{INamedLibraryDispatcher, INamedDispatcherTrait, }; use dojo::world::{IWorldDispatcher, IWorld}; - use super::Context; - const NAME_ENTRYPOINT: felt252 = 0x0361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60; + const WORLD: felt252 = 0; + #[event] #[derive(Drop, starknet::Event)] enum Event { WorldSpawned: WorldSpawned, - ComponentRegistered: ComponentRegistered, - SystemRegistered: SystemRegistered, + ModelRegistered: ModelRegistered, StoreSetRecord: StoreSetRecord, StoreDelRecord: StoreDelRecord } @@ -83,13 +77,7 @@ mod world { } #[derive(Drop, starknet::Event)] - struct ComponentRegistered { - name: felt252, - class_hash: ClassHash - } - - #[derive(Drop, starknet::Event)] - struct SystemRegistered { + struct ModelRegistered { name: felt252, class_hash: ClassHash } @@ -99,7 +87,7 @@ mod world { table: felt252, keys: Span, offset: u8, - value: Span, + values: Span, } #[derive(Drop, starknet::Event)] @@ -111,13 +99,10 @@ mod world { #[storage] struct Storage { executor_dispatcher: IExecutorDispatcher, - components: LegacyMap::, - systems: LegacyMap::, + models: LegacyMap::, nonce: usize, owners: LegacyMap::<(felt252, ContractAddress), bool>, - writers: LegacyMap::<(felt252, felt252), bool>, - // Tracks the origin executor. - call_origin: ContractAddress, + writers: LegacyMap::<(felt252, ContractAddress), bool>, // Tracks the calling systems name for auth purposes. call_stack_len: felt252, call_stack: LegacyMap::, @@ -128,7 +113,9 @@ mod world { self.executor_dispatcher.write(IExecutorDispatcher { contract_address: executor }); self .owners - .write((0, starknet::get_tx_info().unbox().account_contract_address), bool::True(())); + .write( + (WORLD, starknet::get_tx_info().unbox().account_contract_address), bool::True(()) + ); EventEmitter::emit( ref self, @@ -139,6 +126,24 @@ mod world { ); } + /// Call Helper, + /// Call the provided `entrypoint` method on the given `class_hash`. + /// + /// # Arguments + /// + /// * `class_hash` - Class Hash to call. + /// * `entrypoint` - Entrypoint to call. + /// * `calldata` - The calldata to pass. + /// + /// # Returns + /// + /// The return value of the call. + fn class_call( + self: @ContractState, class_hash: ClassHash, entrypoint: felt252, calldata: Span + ) -> Span { + self.executor_dispatcher.read().call(class_hash, entrypoint, calldata) + } + #[external(v0)] impl World of IWorld { /// Checks if the provided account is an owner of the target. @@ -164,14 +169,11 @@ mod world { /// * `target` - The target. fn grant_owner(ref self: ContractState, address: ContractAddress, target: felt252) { let caller = get_caller_address(); - assert( - self.is_owner(caller, target) || self.is_owner(caller, 0), - 'not owner' - ); + assert(self.is_owner(caller, target) || self.is_owner(caller, WORLD), 'not owner'); self.owners.write((target, address), bool::True(())); } - /// Revokes owner permission to the system for the component. + /// Revokes owner permission to the system for the model. /// Can only be called by an existing owner or the world admin. /// /// # Arguments @@ -180,198 +182,93 @@ mod world { /// * `target` - The target. fn revoke_owner(ref self: ContractState, address: ContractAddress, target: felt252) { let caller = get_caller_address(); - assert( - self.is_owner(caller, target) - || self.is_owner(caller, 0), - 'not owner' - ); + assert(self.is_owner(caller, target) || self.is_owner(caller, WORLD), 'not owner'); self.owners.write((target, address), bool::False(())); } - /// Checks if the provided system is a writer of the component. + /// Checks if the provided system is a writer of the model. /// /// # Arguments /// - /// * `component` - The name of the component. + /// * `model` - The name of the model. /// * `system` - The name of the system. /// /// # Returns /// - /// * `bool` - True if the system is a writer of the component, false otherwise - fn is_writer(self: @ContractState, component: felt252, system: felt252) -> bool { - self.writers.read((component, system)) + /// * `bool` - True if the system is a writer of the model, false otherwise + fn is_writer(self: @ContractState, model: felt252, system: ContractAddress) -> bool { + self.writers.read((model, system)) } - /// Grants writer permission to the system for the component. - /// Can only be called by an existing component owner or the world admin. + /// Grants writer permission to the system for the model. + /// Can only be called by an existing model owner or the world admin. /// /// # Arguments /// - /// * `component` - The name of the component. + /// * `model` - The name of the model. /// * `system` - The name of the system. - fn grant_writer(ref self: ContractState, component: felt252, system: felt252) { + fn grant_writer(ref self: ContractState, model: felt252, system: ContractAddress) { let caller = get_caller_address(); assert( - self.is_owner(caller, component) - || self.is_owner(caller, 0), + self.is_owner(caller, model) || self.is_owner(caller, WORLD), 'not owner or writer' ); - self.writers.write((component, system), bool::True(())); + self.writers.write((model, system), bool::True(())); } - /// Revokes writer permission to the system for the component. - /// Can only be called by an existing component writer, owner or the world admin. + /// Revokes writer permission to the system for the model. + /// Can only be called by an existing model writer, owner or the world admin. /// /// # Arguments /// - /// * `component` - The name of the component. + /// * `model` - The name of the model. /// * `system` - The name of the system. - fn revoke_writer(ref self: ContractState, component: felt252, system: felt252) { + fn revoke_writer(ref self: ContractState, model: felt252, system: ContractAddress) { let caller = get_caller_address(); assert( - self.is_writer(component, self.caller_system()) - || self.is_owner(caller, component) - || self.is_owner(caller, 0), + self.is_writer(model, caller) + || self.is_owner(caller, model) + || self.is_owner(caller, WORLD), 'not owner or writer' ); - self.writers.write((component, system), bool::False(())); + self.writers.write((model, system), bool::False(())); } - /// Registers a component in the world. If the component is already registered, + /// Registers a model in the world. If the model is already registered, /// the implementation will be updated. /// /// # Arguments /// - /// * `class_hash` - The class hash of the component to be registered. - fn register_component(ref self: ContractState, class_hash: ClassHash) { + /// * `class_hash` - The class hash of the model to be registered. + fn register_model(ref self: ContractState, class_hash: ClassHash) { let caller = get_caller_address(); let calldata = ArrayTrait::new(); - let name = *self - .executor_dispatcher - .read() - .call(class_hash, NAME_ENTRYPOINT, calldata.span())[0]; - - // If component is already registered, validate permission to update. - if self.components.read(name).is_non_zero() { - assert( - self.is_owner(caller, name), 'only owner can update' - ); - } else { - self.owners.write((name, caller), bool::True(())); - }; - - self.components.write(name, class_hash); - EventEmitter::emit(ref self, ComponentRegistered { name, class_hash }); - } + let name = *class_call(@self, class_hash, NAME_ENTRYPOINT, calldata.span())[0]; - /// Gets the class hash of a registered component. - /// - /// # Arguments - /// - /// * `name` - The name of the component. - /// - /// # Returns - /// - /// * `ClassHash` - The class hash of the component. - fn component(self: @ContractState, name: felt252) -> ClassHash { - self.components.read(name) - } - - /// Registers a system in the world. If the system is already registered, - /// the implementation will be updated. - /// - /// # Arguments - /// - /// * `class_hash` - The class hash of the system to be registered. - fn register_system(ref self: ContractState, class_hash: ClassHash) { - let caller = get_caller_address(); - let calldata = ArrayTrait::new(); - let name = *self - .executor_dispatcher - .read() - .call(class_hash, NAME_ENTRYPOINT, calldata.span())[0]; - - // If system is already registered, validate permission to update. - if self.systems.read(name).is_non_zero() { - assert( - self.is_owner(caller, name), 'only owner can update' - ); + // If model is already registered, validate permission to update. + if self.models.read(name).is_non_zero() { + assert(self.is_owner(caller, name), 'only owner can update'); } else { self.owners.write((name, caller), bool::True(())); }; - self.systems.write(name, class_hash); - EventEmitter::emit(ref self, SystemRegistered { name, class_hash }); + self.models.write(name, class_hash); + EventEmitter::emit(ref self, ModelRegistered { name, class_hash }); } - /// Gets the class hash of a registered system. + /// Gets the class hash of a registered model. /// /// # Arguments /// - /// * `name` - The name of the system. + /// * `name` - The name of the model. /// /// # Returns /// - /// * `ClassHash` - The class hash of the system. - fn system(self: @ContractState, name: felt252) -> ClassHash { - self.systems.read(name) - } - - /// Executes a system with the given calldata. - /// - /// # Arguments - /// - /// * `system` - The name of the system to be executed. - /// * `calldata` - The calldata to be passed to the system. - /// - /// # Returns - /// - /// * `Span` - The result of the system execution. - fn execute( - ref self: ContractState, system: felt252, mut calldata: Array - ) -> Span { - let stack_len = self.call_stack_len.read(); - self.call_stack.write(stack_len, system); - self.call_stack_len.write(stack_len + 1); - - // Get the class hash of the system to be executed - let system_class_hash = self.systems.read(system); - - // If this is the initial call, set the origin to the caller - let mut call_origin = self.call_origin.read(); - if stack_len.is_zero() { - call_origin = get_caller_address(); - self.call_origin.write(call_origin); - } - - let ctx = Context { - world: IWorldDispatcher { - contract_address: get_contract_address() - }, origin: self.call_origin.read(), system, system_class_hash, - }; - - // Add context to calldata - ctx.serialize(ref calldata); - - // Call the system via executor - let res = self - .executor_dispatcher - .read() - .execute(ctx.system_class_hash, calldata.span()); - - // Reset the current call stack frame - self.call_stack.write(stack_len, 0); - // Decrement the call stack pointer - self.call_stack_len.write(stack_len); - - // If this is the initial call, reset the origin on exit - if stack_len.is_zero() { - self.call_origin.write(starknet::contract_address_const::<0x0>()); - } - - res + /// * `ClassHash` - The class hash of the model. + fn model(self: @ContractState, name: felt252) -> ClassHash { + self.models.read(name) } /// Issues an autoincremented id to the caller. @@ -392,76 +289,85 @@ mod world { /// * `keys` - The keys of the event. /// * `values` - The data to be logged by the event. fn emit(self: @ContractState, mut keys: Array, values: Span) { - self.caller_system().serialize(ref keys); + let system = get_caller_address(); + system.serialize(ref keys); emit_event_syscall(keys.span(), values).unwrap_syscall(); } - /// Sets the component value for an entity. + /// Sets the model value for an entity. /// /// # Arguments /// - /// * `component` - The name of the component to be set. + /// * `model` - The name of the model to be set. /// * `key` - The key to be used to find the entity. - /// * `offset` - The offset of the component in the entity. + /// * `offset` - The offset of the model in the entity. /// * `value` - The value to be set. fn set_entity( ref self: ContractState, - component: felt252, + model: felt252, keys: Span, offset: u8, - value: Span + values: Span, + layout: Span ) { - assert_can_write(@self, component); + assert_can_write(@self, model, get_caller_address()); let key = poseidon::poseidon_hash_span(keys); - let component_class_hash = self.components.read(component); - database::set(component_class_hash, component, key, offset, value); + let model_class_hash = self.models.read(model); + database::set(model_class_hash, model, key, offset, values, layout); - EventEmitter::emit(ref self, StoreSetRecord { table: component, keys, offset, value }); + EventEmitter::emit(ref self, StoreSetRecord { table: model, keys, offset, values }); } - /// Deletes a component from an entity. + /// Deletes a model from an entity. /// /// # Arguments /// - /// * `component` - The name of the component to be deleted. + /// * `model` - The name of the model to be deleted. /// * `query` - The query to be used to find the entity. - fn delete_entity(ref self: ContractState, component: felt252, keys: Span) { - assert_can_write(@self, component); + fn delete_entity(ref self: ContractState, model: felt252, keys: Span) { + let system = get_caller_address(); + assert(system.is_non_zero(), 'must be called thru system'); + assert_can_write(@self, model, system); let key = poseidon::poseidon_hash_span(keys); - let component_class_hash = self.components.read(component); - database::del(component_class_hash, component, key); + let model_class_hash = self.models.read(model); + database::del(model_class_hash, model, key); - EventEmitter::emit(ref self, StoreDelRecord { table: component, keys }); + EventEmitter::emit(ref self, StoreDelRecord { table: model, keys }); } - /// Gets the component value for an entity. Returns a zero initialized - /// component value if the entity has not been set. + /// Gets the model value for an entity. Returns a zero initialized + /// model value if the entity has not been set. /// /// # Arguments /// - /// * `component` - The name of the component to be retrieved. + /// * `model` - The name of the model to be retrieved. /// * `query` - The query to be used to find the entity. - /// * `offset` - The offset of the component values. - /// * `length` - The length of the component values. + /// * `offset` - The offset of the model values. + /// * `length` - The length of the model values. /// /// # Returns /// - /// * `Span` - The value of the component, zero initialized if not set. + /// * `Span` - The value of the model, zero initialized if not set. fn entity( - self: @ContractState, component: felt252, keys: Span, offset: u8, length: usize + self: @ContractState, + model: felt252, + keys: Span, + offset: u8, + length: usize, + layout: Span ) -> Span { - let class_hash = self.components.read(component); + let class_hash = self.models.read(model); let key = poseidon::poseidon_hash_span(keys); - database::get(class_hash, component, key, offset, length) + database::get(class_hash, model, key, offset, length, layout) } - /// Returns entity IDs and entities that contain the component state. + /// Returns entity IDs and entities that contain the model state. /// /// # Arguments /// - /// * `component` - The name of the component to be retrieved. + /// * `model` - The name of the model to be retrieved. /// * `index` - The index to be retrieved. /// /// # Returns @@ -469,10 +375,14 @@ mod world { /// * `Span` - The entity IDs. /// * `Span>` - The entities. fn entities( - self: @ContractState, component: felt252, index: felt252, length: usize + self: @ContractState, + model: felt252, + index: felt252, + length: usize, + layout: Span ) -> (Span, Span>) { - let class_hash = self.components.read(component); - database::all(class_hash, component.into(), index, length) + let class_hash = self.models.read(model); + database::all(class_hash, model.into(), index, length, layout) } /// Sets the executor contract address. @@ -482,7 +392,7 @@ mod world { /// * `contract_address` - The contract address of the executor. fn set_executor(ref self: ContractState, contract_address: ContractAddress) { // Only owner can set executor - assert(self.is_owner(get_caller_address(), 0), 'only owner can set executor'); + assert(self.is_owner(get_caller_address(), WORLD), 'only owner can set executor'); self .executor_dispatcher .write(IExecutorDispatcher { contract_address: contract_address }); @@ -496,51 +406,20 @@ mod world { fn executor(self: @ContractState) -> ContractAddress { self.executor_dispatcher.read().contract_address } - - /// Gets the origin caller. - /// - /// # Returns - /// - /// * `felt252` - The origin caller. - fn origin(self: @ContractState) -> ContractAddress { - self.call_origin.read() - } - - /// Gets the caller system's name. - /// - /// # Returns - /// - /// * `felt252` - The caller system's name. - fn caller_system(self: @ContractState) -> felt252 { - self.call_stack.read(self.call_stack_len.read() - 1) - } } - /// Asserts that the current caller can write to the component. + /// Asserts that the current caller can write to the model. /// /// # Arguments /// - /// * `component` - The name of the component being written to. - fn assert_can_write(self: @ContractState, component: felt252) { - assert( - get_caller_address() == self.executor_dispatcher.read().contract_address, - 'must be called thru executor' - ); - + /// * `model` - The name of the model being written to. + /// * `caller` - The name of the caller writing. + fn assert_can_write(self: @ContractState, model: felt252, caller: ContractAddress) { assert( - IWorld::is_writer(self, component, self.caller_system()) - || IWorld::is_owner(self, get_tx_info().unbox().account_contract_address, component) - || IWorld::is_owner(self, get_tx_info().unbox().account_contract_address, 0), + IWorld::is_writer(self, model, caller) + || IWorld::is_owner(self, get_tx_info().unbox().account_contract_address, model) + || IWorld::is_owner(self, get_tx_info().unbox().account_contract_address, WORLD), 'not writer' ); } } - -#[system] -mod library_call { - fn execute( - class_hash: starknet::ClassHash, entrypoint: felt252, calladata: Span - ) -> Span { - starknet::syscalls::library_call_syscall(class_hash, entrypoint, calladata).unwrap_syscall() - } -} diff --git a/crates/dojo-core/src/world_factory.cairo b/crates/dojo-core/src/world_factory.cairo deleted file mode 100644 index 716517084f..0000000000 --- a/crates/dojo-core/src/world_factory.cairo +++ /dev/null @@ -1,177 +0,0 @@ -use array::ArrayTrait; - -use starknet::{ClassHash, ContractAddress}; - -#[starknet::interface] -trait IWorldFactory { - fn set_world(ref self: T, class_hash: ClassHash); - fn set_executor(ref self: T, executor_address: ContractAddress); - fn spawn( - ref self: T, components: Array, systems: Array, - ) -> ContractAddress; - fn world(self: @T) -> ClassHash; - fn executor(self: @T) -> ContractAddress; -} - -#[starknet::contract] -mod world_factory { - use array::ArrayTrait; - use option::OptionTrait; - use traits::Into; - - use starknet::{ - ClassHash, ContractAddress, contract_address::ContractAddressIntoFelt252, - syscalls::deploy_syscall, get_caller_address - }; - - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - - use super::IWorldFactory; - - #[storage] - struct Storage { - world_class_hash: ClassHash, - executor_address: ContractAddress, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - WorldSpawned: WorldSpawned - } - - #[derive(Drop, starknet::Event)] - struct WorldSpawned { - address: ContractAddress - } - - #[constructor] - fn constructor( - ref self: ContractState, world_class_hash_: ClassHash, executor_address_: ContractAddress, - ) { - self.world_class_hash.write(world_class_hash_); - self.executor_address.write(executor_address_); - } - - #[external(v0)] - impl WorldFactory of IWorldFactory { - /// Spawns a new world with the given components and systems. - /// - /// # Arguments - /// - /// * `components` - The components to be registered. - /// * `systems` - The systems to be registered. - /// - /// # Returns - /// - /// The address of the deployed world. - fn spawn( - ref self: ContractState, components: Array, systems: Array, - ) -> ContractAddress { - // deploy world - let mut world_constructor_calldata: Array = ArrayTrait::new(); - world_constructor_calldata.append(self.executor_address.read().into()); - let world_class_hash = self.world_class_hash.read(); - let result = deploy_syscall( - world_class_hash, 0, world_constructor_calldata.span(), true - ); - let (world_address, _) = result.unwrap_syscall(); - let world = IWorldDispatcher { contract_address: world_address }; - - // events - self.emit(WorldSpawned { address: world_address }); - - // register components - let components_len = components.len(); - register_components(@self, components, components_len, 0, world_address); - - // register systems - let systems_len = systems.len(); - register_systems(@self, systems, systems_len, 0, world_address); - - return world_address; - } - - /// Sets the executor address. - /// - /// # Arguments - /// - /// * `executor_address` - The address of the executor. - fn set_executor(ref self: ContractState, executor_address: ContractAddress) { - self.executor_address.write(executor_address); - } - - /// Sets the class hash for the world. - /// - /// # Arguments - /// - /// * `class_hash` - The class hash of world. - fn set_world(ref self: ContractState, class_hash: ClassHash) { - self.world_class_hash.write(class_hash); - } - - /// Gets the executor contract address. - /// - /// # Returns - /// - /// * `ContractAddress` - The address of the executor contract. - fn executor(self: @ContractState) -> ContractAddress { - return self.executor_address.read(); - } - - /// Gets the world class hash. - /// - /// # Returns - /// - /// * `ClassHash` - The class hash of the world. - fn world(self: @ContractState) -> ClassHash { - return self.world_class_hash.read(); - } - } - - /// Registers all the given components in the world at the given address. - /// - /// # Arguments - /// - /// * `components` - The components to be registered. - /// * `components_len` - The number of components to register. - /// * `index` - The index where to start the registration in the components list. - /// * `world_address` - The address of the world where components are registered. - fn register_components( - self: @ContractState, - components: Array, - components_len: usize, - index: usize, - world_address: ContractAddress - ) { - if (index == components_len) { - return (); - } - IWorldDispatcher { - contract_address: world_address - }.register_component(*components.at(index)); - return register_components(self, components, components_len, index + 1, world_address); - } - - /// Registers all the given systems in the world at the given address. - /// - /// # Arguments - /// - /// * `systems` - The systems to be registered. - /// * `systems_len` - The number of systems to register. - /// * `index` - The index where to start the registration in the system list. - /// * `world_address` - The address of the world where systems are registered. - fn register_systems( - self: @ContractState, - systems: Array, - systems_len: usize, - index: usize, - world_address: ContractAddress - ) { - if (index == systems_len) { - return (); - } - IWorldDispatcher { contract_address: world_address }.register_system(*systems.at(index)); - return register_systems(self, systems, systems_len, index + 1, world_address); - } -} diff --git a/crates/dojo-core/src/world_factory_test.cairo b/crates/dojo-core/src/world_factory_test.cairo deleted file mode 100644 index 07f1b22e41..0000000000 --- a/crates/dojo-core/src/world_factory_test.cairo +++ /dev/null @@ -1,98 +0,0 @@ -use core::traits::Into; -use core::result::ResultTrait; -use array::{ArrayTrait, SpanTrait}; -use clone::Clone; -use option::OptionTrait; -use traits::TryInto; -use serde::Serde; -use debug::PrintTrait; -use starknet::syscalls::deploy_syscall; -use starknet::get_caller_address; -use starknet::class_hash::ClassHash; -use starknet::class_hash::Felt252TryIntoClassHash; - -use dojo::executor::executor; -use dojo::world_factory::{IWorldFactoryDispatcher, IWorldFactoryDispatcherTrait, world_factory}; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, world}; - - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Foo { - #[key] - id: felt252, - a: felt252, - b: u128, -} - -#[system] -mod bar { - use super::Foo; - - fn execute(foo: Foo) -> Foo { - foo - } -} - -#[test] -#[available_gas(40000000)] -fn test_constructor() { - let mut calldata: Array = array::ArrayTrait::new(); - calldata.append(starknet::class_hash_const::<0x420>().into()); - calldata.append(starknet::contract_address_const::<0x69>().into()); - - let (factory_address, _) = deploy_syscall( - world_factory::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); - - let factory = IWorldFactoryDispatcher { contract_address: factory_address }; - - assert(factory.world() == starknet::class_hash_const::<0x420>(), 'wrong world class hash'); - assert( - factory.executor() == starknet::contract_address_const::<0x69>(), 'wrong executor contract' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_spawn_world() { - // Deploy Executor - let constructor_calldata = array::ArrayTrait::new(); - let (executor_address, _) = deploy_syscall( - executor::TEST_CLASS_HASH.try_into().unwrap(), 0, constructor_calldata.span(), false - ) - .unwrap(); - - // WorldFactory constructor - let mut calldata: Array = array::ArrayTrait::new(); - calldata.append(world::TEST_CLASS_HASH); - calldata.append(executor_address.into()); - - let (factory_address, _) = deploy_syscall( - world_factory::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); - - let factory = IWorldFactoryDispatcher { contract_address: factory_address }; - - assert(factory.executor() == executor_address, 'wrong executor address'); - assert(factory.world() == world::TEST_CLASS_HASH.try_into().unwrap(), 'wrong world class hash'); - - // Prepare components and systems - let mut systems: Array = array::ArrayTrait::new(); - systems.append(bar::TEST_CLASS_HASH.try_into().unwrap()); - - let mut components: Array = array::ArrayTrait::new(); - components.append(foo::TEST_CLASS_HASH.try_into().unwrap()); - - // Spawn World from WorldFactory - let world_address = factory.spawn(components, systems); - let world = IWorldDispatcher { contract_address: world_address }; - - // Check Foo component and Bar system are registered - let foo_hash = world.component('Foo'.into()); - assert(foo_hash == foo::TEST_CLASS_HASH.try_into().unwrap(), 'component not registered'); - - let bar_hash = world.system('bar'.into()); - assert(bar_hash == bar::TEST_CLASS_HASH.try_into().unwrap(), 'system not registered'); -} diff --git a/crates/dojo-core/src/world_test.cairo b/crates/dojo-core/src/world_test.cairo index 929d8c6fa5..5eac4ebb1f 100644 --- a/crates/dojo-core/src/world_test.cairo +++ b/crates/dojo-core/src/world_test.cairo @@ -1,22 +1,18 @@ -use array::ArrayTrait; -use array::SpanTrait; +use array::{ArrayTrait, SpanTrait}; use clone::Clone; use core::result::ResultTrait; -use traits::Into; -use traits::TryInto; +use traits::{Into, TryInto}; use option::OptionTrait; use starknet::class_hash::Felt252TryIntoClassHash; -use starknet::contract_address_const; -use starknet::ContractAddress; -use starknet::get_caller_address; +use starknet::{contract_address_const, ContractAddress, ClassHash, get_caller_address}; use starknet::syscalls::deploy_syscall; use dojo::executor::executor; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, library_call, world}; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, world}; +use dojo::database::schema::SchemaIntrospection; +use dojo::test_utils::{spawn_test_world, deploy_with_world_address}; -// Components and Systems - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] +#[derive(Model, Copy, Drop, Serde)] struct Foo { #[key] caller: ContractAddress, @@ -24,22 +20,38 @@ struct Foo { b: u128, } -#[derive(Component, Copy, Drop, Serde, SerdeLen)] +#[derive(Model, Copy, Drop, Serde)] struct Fizz { #[key] caller: ContractAddress, a: felt252 } -#[system] +#[starknet::interface] +trait Ibar { + fn set_foo(self: @TContractState, a: felt252, b: u128); +} + +#[starknet::contract] mod bar { - use super::Foo; + use super::{Foo, IWorldDispatcher, IWorldDispatcherTrait}; use traits::Into; - use starknet::get_caller_address; - use dojo::world::Context; + use starknet::{get_caller_address, ContractAddress}; - fn execute(ctx: Context, a: felt252, b: u128) { - set !(ctx.world, Foo { caller: ctx.origin, a, b }); + #[storage] + struct Storage { + world: IWorldDispatcher, + } + #[constructor] + fn constructor(ref self: ContractState, world: ContractAddress) { + self.world.write(IWorldDispatcher { contract_address: world }) + } + + #[external(v0)] + impl IbarImpl of super::Ibar { + fn set_foo(self: @ContractState, a: felt252, b: u128) { + set!(self.world.read(), Foo { caller: get_caller_address(), a, b }); + } } } @@ -47,11 +59,9 @@ mod bar { #[test] #[available_gas(2000000)] -fn test_component() { - let name = 'Foo'; +fn test_model() { let world = deploy_world(); - - world.register_component(foo::TEST_CLASS_HASH.try_into().unwrap()); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); } #[test] @@ -59,20 +69,28 @@ fn test_component() { fn test_system() { // Spawn empty world let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - world.register_system(bar::TEST_CLASS_HASH.try_into().unwrap()); - world.register_component(foo::TEST_CLASS_HASH.try_into().unwrap()); - let mut data = ArrayTrait::new(); - data.append(1337); - data.append(1337); - let id = world.uuid(); - world.execute('bar', data); + // System contract + let bar_contract = IbarDispatcher { + contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) + }; - let mut keys = ArrayTrait::new(); - keys.append(0); + bar_contract.set_foo(1337, 1337); - let stored = world.entity('Foo', keys.span(), 0, dojo::SerdeLen::::len()); - assert(*stored.snapshot.at(0) == 1337, 'data not stored'); + let stored: Foo = get!(world, get_caller_address(), Foo); + assert(stored.a == 1337, 'data not stored'); + assert(stored.b == 1337, 'data not stored'); +} + +#[test] +#[available_gas(6000000)] +fn test_model_class_hash_getter() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = world.model('Foo'); + assert(foo == foo::TEST_CLASS_HASH.try_into().unwrap(), 'foo does not exists'); } #[test] @@ -93,23 +111,20 @@ fn test_emit() { fn test_set_entity_admin() { // Spawn empty world let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - world.register_system(bar::TEST_CLASS_HASH.try_into().unwrap()); - world.register_component(foo::TEST_CLASS_HASH.try_into().unwrap()); + let bar_contract = IbarDispatcher { + contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) + }; let alice = starknet::contract_address_const::<0x1337>(); starknet::testing::set_contract_address(alice); - let mut keys = array::ArrayTrait::new(); - keys.append(alice.into()); + bar_contract.set_foo(420, 1337); - let mut data = ArrayTrait::new(); - data.append(420); - data.append(1337); - world.execute('bar', data); - let foo = world.entity('Foo', keys.span(), 0, dojo::SerdeLen::::len()); - assert(*foo[0] == 420, 'data not stored'); - assert(*foo[1] == 1337, 'data not stored'); + let foo: Foo = get!(world, alice, Foo); + assert(foo.a == 420, 'data not stored'); + assert(foo.b == 1337, 'data not stored'); } #[test] @@ -119,69 +134,40 @@ fn test_set_entity_unauthorized() { // Spawn empty world let world = deploy_world(); - world.register_system(bar::TEST_CLASS_HASH.try_into().unwrap()); - world.register_component(foo::TEST_CLASS_HASH.try_into().unwrap()); + let bar_contract = IbarDispatcher { + contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) + }; + + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); let caller = starknet::contract_address_const::<0x1337>(); starknet::testing::set_account_contract_address(caller); // Call bar system, should panic as it's not authorized - let mut data = ArrayTrait::new(); - data.append(420); - data.append(1337); - world.execute('bar', data); + bar_contract.set_foo(420, 1337); } -#[test] -#[available_gas(8000000)] -#[should_panic] -fn test_set_entity_directly() { - // Spawn empty world - let world = deploy_world(); +// This test is probably irrelevant now because we have no systems, +// so all `set_entity` call are from arbitrary contracts. +// Owners can still update via unregistered contracts/call from account +// #[test] +// #[available_gas(8000000)] +// #[should_panic] +// fn test_set_entity_directly() { +// // Spawn world +// let world = deploy_world(); +// world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); - world.register_system(bar::TEST_CLASS_HASH.try_into().unwrap()); - world.register_component(foo::TEST_CLASS_HASH.try_into().unwrap()); +// let bar_contract = IbarDispatcher { +// contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world) +// }; - set !(world, Foo { caller: starknet::contract_address_const::<0x1337>(), a: 420, b: 1337 }); -} +// set!(world, Foo { caller: starknet::contract_address_const::<0x1337>(), a: 420, b: 1337 }); +// } // Utils fn deploy_world() -> IWorldDispatcher { - // Deploy executor contract - let executor_constructor_calldata = array::ArrayTrait::new(); - let (executor_address, _) = deploy_syscall( - executor::TEST_CLASS_HASH.try_into().unwrap(), - 0, - executor_constructor_calldata.span(), - false - ) - .unwrap(); - - // Deploy world contract - let mut constructor_calldata = array::ArrayTrait::new(); - constructor_calldata.append(executor_address.into()); - let (world_address, _) = deploy_syscall( - world::TEST_CLASS_HASH.try_into().unwrap(), 0, constructor_calldata.span(), false - ) - .unwrap(); - let world = IWorldDispatcher { contract_address: world_address }; - - world -} - -#[test] -#[available_gas(6000000)] -fn test_library_call_system() { - // Spawn empty world - let world = deploy_world(); - - world.register_system(library_call::TEST_CLASS_HASH.try_into().unwrap()); - let mut calldata = ArrayTrait::new(); - calldata.append(foo::TEST_CLASS_HASH); - // 'name' entrypoint - calldata.append(0x0361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60); - calldata.append(0); - world.execute('library_call', calldata); + spawn_test_world(array![]) } #[test] @@ -215,7 +201,6 @@ fn test_set_owner_fails_for_non_owner() { let world = deploy_world(); let alice = starknet::contract_address_const::<0x1337>(); - starknet::testing::set_contract_address(alice); world.revoke_owner(alice, 0); @@ -229,13 +214,49 @@ fn test_set_owner_fails_for_non_owner() { fn test_writer() { let world = deploy_world(); - assert(!world.is_writer(42, 69), 'should not be writer'); + assert(!world.is_writer(42, 69.try_into().unwrap()), 'should not be writer'); - world.grant_writer(42, 69); - assert(world.is_writer(42, 69), 'should be writer'); + world.grant_writer(42, 69.try_into().unwrap()); + assert(world.is_writer(42, 69.try_into().unwrap()), 'should be writer'); - world.revoke_writer(42, 69); - assert(!world.is_writer(42, 69), 'should not be writer'); + world.revoke_writer(42, 69.try_into().unwrap()); + assert(!world.is_writer(42, 69.try_into().unwrap()), 'should not be writer'); +} + +#[test] +#[available_gas(6000000)] +#[should_panic] +fn test_system_not_writer_fail() { + let world = spawn_test_world(array![foo::TEST_CLASS_HASH],); + + let bar_address = deploy_with_world_address(bar::TEST_CLASS_HASH, world); + let bar_contract = IbarDispatcher { contract_address: bar_address }; + + // Caller is not owner now + let caller = starknet::contract_address_const::<0x1337>(); + starknet::testing::set_account_contract_address(caller); + + // Should panic, system not writer + bar_contract.set_foo(25, 16); +} + +#[test] +#[available_gas(6000000)] +fn test_system_writer_access() { + let world = spawn_test_world(array![foo::TEST_CLASS_HASH],); + + let bar_address = deploy_with_world_address(bar::TEST_CLASS_HASH, world); + let bar_contract = IbarDispatcher { contract_address: bar_address }; + + world.grant_writer('Foo', bar_address); + assert(world.is_writer('Foo', bar_address), 'should be writer'); + + // Caller is not owner now + let caller = starknet::contract_address_const::<0x1337>(); + starknet::testing::set_account_contract_address(caller); + + // Should not panic, system is writer + bar_contract.set_foo(25, 16); } #[test] @@ -246,48 +267,71 @@ fn test_set_writer_fails_for_non_owner() { let alice = starknet::contract_address_const::<0x1337>(); starknet::testing::set_contract_address(alice); + assert(!world.is_owner(alice, 0), 'should not be owner'); - world.grant_writer(42, 69); + world.grant_writer(42, 69.try_into().unwrap()); } -#[system] + +#[starknet::interface] +trait IOrigin { + fn assert_origin(self: @TContractState); +} + +#[starknet::contract] mod origin { - use dojo::world::Context; + use super::{IWorldDispatcher, ContractAddress}; - fn execute(ctx: Context) { - assert(ctx.origin == starknet::contract_address_const::<0x1337>(), 'should be equal'); + #[storage] + struct Storage { + world: IWorldDispatcher, } -} -#[system] -mod origin_wrapper { - use traits::Into; - use array::ArrayTrait; - use dojo::world::Context; - - fn execute(ctx: Context) { - let data: Array = ArrayTrait::new(); - assert(ctx.origin == starknet::contract_address_const::<0x1337>(), 'should be equal'); - ctx.world.execute('origin', data); - assert(ctx.origin == starknet::contract_address_const::<0x1337>(), 'should be equal'); + #[constructor] + fn constructor(ref self: ContractState, world: ContractAddress) { + self.world.write(IWorldDispatcher { contract_address: world }) + } + + #[external(v0)] + impl IOriginImpl of super::IOrigin { + fn assert_origin(self: @ContractState) { + assert( + starknet::get_caller_address() == starknet::contract_address_const::<0x1337>(), + 'should be equal' + ); + } } } #[test] -#[available_gas(6000000)] -fn test_execute_origin() { - // Spawn empty world - let world = deploy_world(); +#[available_gas(60000000)] +fn test_execute_multiple_worlds() { + // Deploy world contract + let world1 = spawn_test_world(array![foo::TEST_CLASS_HASH],); + + let bar1_contract = IbarDispatcher { + contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world1) + }; - world.register_system(origin::TEST_CLASS_HASH.try_into().unwrap()); - world.register_system(origin_wrapper::TEST_CLASS_HASH.try_into().unwrap()); - world.register_component(foo::TEST_CLASS_HASH.try_into().unwrap()); - let data = ArrayTrait::new(); + // Deploy another world contract + let world2 = spawn_test_world(array![foo::TEST_CLASS_HASH],); + + let bar2_contract = IbarDispatcher { + contract_address: deploy_with_world_address(bar::TEST_CLASS_HASH, world2) + }; let alice = starknet::contract_address_const::<0x1337>(); starknet::testing::set_contract_address(alice); - assert(world.origin() == starknet::contract_address_const::<0x0>(), 'should be equal'); - world.execute('origin_wrapper', data); - assert(world.origin() == starknet::contract_address_const::<0x0>(), 'should be equal'); + + bar1_contract.set_foo(1337, 1337); + bar2_contract.set_foo(7331, 7331); + + let mut keys = ArrayTrait::new(); + keys.append(0); + + let data1 = get!(world1, alice, Foo); + let data2 = get!(world2, alice, Foo); + assert(data1.a == 1337, 'data1 not stored'); + assert(data2.a == 7331, 'data2 not stored'); } diff --git a/crates/dojo-defi/README.md b/crates/dojo-defi/README.md new file mode 100644 index 0000000000..d806f17ce4 --- /dev/null +++ b/crates/dojo-defi/README.md @@ -0,0 +1,173 @@ +# Gradual Dutch Auctions (GDA) + +## Introduction + +Gradual Dutch Auctions (GDA) enable efficient sales of assets without relying on liquid markets. GDAs offer a novel solution for selling both non-fungible tokens (NFTs) and fungible tokens through discrete and continuous mechanisms. + +## Discrete GDA + +### Motivation + +Discrete GDAs are perfect for selling NFTs in integer quantities. They offer an efficient way to conduct bulk purchases through a sequence of Dutch auctions. + +### Mechanism + +The process involves holding virtual Dutch auctions for each token, allowing for efficient clearing of batches. Price decay is exponential, controlled by a decay constant, and the starting price increases by a fixed scale factor. + +### Calculating Batch Purchase Prices + +Calculations can be made efficiently for purchasing a batch of auctions, following a given price function. + +## Continuous GDA + +### Motivation + +Continuous GDAs offer a mechanism for selling fungible tokens, allowing for constant rate emissions over time. + +### Mechanism + +The process works by incrementally making more assets available for sale, splitting sales into an infinite sequence of auctions. Various price functions, including exponential decay, can be applied. + +### Calculating Purchase Prices + +It's possible to compute the purchase price for any quantity of tokens gas-efficiently, using specific mathematical expressions. + +## How to Use + +### Discrete Gradual Dutch Auction + +The `DiscreteGDA` structure represents a Gradual Dutch Auction using discrete time steps. Here's how you can use it: + +#### Creating a Discrete GDA + +```rust +let gda = DiscreteGDA { + sold: Fixed::new_unscaled(0), + initial_price: Fixed::new_unscaled(100, false), + scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), // 1.1 + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), // 0.5, +}; +``` + +#### Calculating the Purchase Price + +You can calculate the purchase price for a specific quantity at a given time using the `purchase_price` method. + +```rust +let time_since_start = FixedTrait::new(2, false); // 2 days since the start, it must be scaled to avoid overflow. +let quantity = FixedTrait::new_unscaled(5, false); // Quantity to purchase +let price = gda.purchase_price(time_since_start, quantity); +``` + +### Continuous Gradual Dutch Auction + +The `ContinuousGDA` structure represents a Gradual Dutch Auction using continuous time steps. + +#### Creating a Continuous GDA + +```rust +let gda = ContinuousGDA { + initial_price: FixedTrait::new_unscaled(1000, false), + emission_rate: FixedTrait::ONE(), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), +}; +``` + +#### Calculating the Purchase Price + +Just like with the discrete version, you can calculate the purchase price for a specific quantity at a given time using the `purchase_price` method. + +```rust +let time_since_last = FixedTrait::new(1, false); // 1 day since the last purchase, it must be scaled to avoid overflow. +let quantity = FixedTrait::new_unscaled(3, false); // Quantity to purchase +let price = gda.purchase_price(time_since_last, quantity); +``` + +--- + +These examples demonstrate how to create instances of the `DiscreteGDA` and `ContinuousGDA` structures, and how to utilize their `purchase_price` methods to calculate the price for purchasing specific quantities at given times. + +You'll need to include the `cubit` crate in your project to work with the `Fixed` type and mathematical operations like `exp` and `pow`. Make sure to follow the respective documentation for additional details and proper integration into your project. + +## Conclusion + +GDAs present a powerful tool for selling both fungible and non-fungible tokens in various contexts. They offer efficient, flexible solutions for asset sales, opening doors to innovative applications beyond traditional markets. + +# Variable Rate GDAs (VRGDAs) + +## Overview + +Variable Rate GDAs (VRGDAs) enable the selling of tokens according to a custom schedule, raising or lowering prices based on the sales pace. VRGDA is a generalization of the GDA mechanism. + +## How to Use + +### Linear Variable Rate Gradual Dutch Auction (LinearVRGDA) + +The `LinearVRGDA` struct represents a linear auction where the price decays based on the target price, decay constant, and per-time-unit rate. + +#### Creating a LinearVRGDA instance + +```rust +const _69_42: u128 = 1280572973596917000000; +const _0_31: u128 = 5718490662849961000; + +let auction = LinearVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + per_time_unit: FixedTrait::new_unscaled(2, false), +}; +``` + +#### Calculating Target Sale Time + +```rust +let target_sale_time = auction.get_target_sale_time(sold_quantity); +``` + +#### Calculating VRGDA Price + +```rust +let price = auction.get_vrgda_price(time_since_start, sold_quantity); +``` + +### Logistic Variable Rate Gradual Dutch Auction (LogisticVRGDA) + +The `LogisticVRGDA` struct represents an auction where the price decays according to a logistic function, based on the target price, decay constant, max sellable quantity, and time scale. + +#### Creating a LogisticVRGDA instance + +```rust +const MAX_SELLABLE: u128 = 6392; +const _0_0023: u128 = 42427511369531970; + +let auction = LogisticVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), + time_scale: FixedTrait::new(_0_0023, false), +}; +``` + +#### Calculating Target Sale Time + +```rust +let target_sale_time = auction.get_target_sale_time(sold_quantity); +``` + +#### Calculating VRGDA Price + +```rust +let price = auction.get_vrgda_price(time_since_start, sold_quantity); +``` + +Make sure to import the required dependencies at the beginning of your Cairo file: + +```rust +use cubit::f128::types::fixed::{Fixed, FixedTrait}; +``` + +These examples show you how to create instances of both `LinearVRGDA` and `LogisticVRGDA` and how to use their methods to calculate the target sale time and VRGDA price. + +## Conclusion + +VRGDAs offer a flexible way to issue NFTs on nearly any schedule, enabling seamless purchases at any time. diff --git a/crates/dojo-defi/Scarb.toml b/crates/dojo-defi/Scarb.toml index 3910c5bcfd..864e87ee60 100644 --- a/crates/dojo-defi/Scarb.toml +++ b/crates/dojo-defi/Scarb.toml @@ -1,11 +1,14 @@ [package] -cairo-version = "2.1.0-rc4" +cairo-version = "2.2.0" description = "Implementations of a defi primitives for the Dojo framework" name = "dojo_defi" version = "0.2.1" +[lib] + [dependencies] cubit = { git = "https://github.com/influenceth/cubit" } + dojo = { path = "../dojo-core" } [[target.dojo]] diff --git a/crates/dojo-defi/cairo_project.toml b/crates/dojo-defi/cairo_project.toml deleted file mode 100644 index cf7ed1d24f..0000000000 --- a/crates/dojo-defi/cairo_project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[crate_roots] -dojo_defi = "src" \ No newline at end of file diff --git a/crates/dojo-defi/src/constant_product_market.cairo b/crates/dojo-defi/src/constant_product_market.cairo deleted file mode 100644 index 98d784c920..0000000000 --- a/crates/dojo-defi/src/constant_product_market.cairo +++ /dev/null @@ -1,2 +0,0 @@ -mod components; -mod systems; diff --git a/crates/dojo-defi/src/constant_product_market/components.cairo b/crates/dojo-defi/src/constant_product_market/components.cairo deleted file mode 100644 index 587334325a..0000000000 --- a/crates/dojo-defi/src/constant_product_market/components.cairo +++ /dev/null @@ -1,419 +0,0 @@ -use traits::{Into, TryInto}; -use option::OptionTrait; - -// Cubit fixed point math library -use cubit::types::fixed::{Fixed, FixedInto, FixedTrait, ONE_u128}; - - -use cubit::test::helpers::assert_precise; - -const SCALING_FACTOR: u128 = 10000; - -#[derive(Component, Copy, Drop, Serde)] -struct Cash { - amount: u128, -} - -#[derive(Component, Copy, Drop, Serde)] -struct Item { - quantity: usize, -} - -#[derive(Component, Copy, Drop, Serde)] -struct Liquidity { - shares: Fixed, -} - -#[derive(Component, Copy, Drop, Serde)] -struct Market { - cash_amount: u128, - item_quantity: usize, -} - -trait MarketTrait { - fn buy(self: @Market, quantity: usize) -> u128; - fn sell(self: @Market, quantity: usize) -> u128; - fn get_reserves(self: @Market) -> (u128, u128); - fn liquidity(self: @Market) -> Fixed; - fn has_liquidity(self: @Market) -> bool; - fn quote_quantity(self: @Market, amount: u128) -> usize; - fn quote_amount(self: @Market, quantity: usize) -> u128; - fn add_liquidity_inner(self: @Market, amount: u128, quantity: usize) -> (u128, usize); - fn add_liquidity(self: @Market, amount: u128, quantity: usize) -> (u128, usize, Fixed); - fn mint_shares(self: @Market, amount: u128, quantity: usize) -> Fixed; - fn remove_liquidity(self: @Market, shares: Fixed) -> (u128, usize); -} - -impl MarketImpl of MarketTrait { - fn buy(self: @Market, quantity: usize) -> u128 { - assert(quantity < *self.item_quantity, 'not enough liquidity'); - let (quantity, available, cash) = normalize(quantity, self); - let k = cash * available; - let cost = (k / (available - quantity)) - cash; - cost - } - - fn sell(self: @Market, quantity: usize) -> u128 { - let (quantity, available, cash) = normalize(quantity, self); - let k = cash * available; - let payout = cash - (k / (available + quantity)); - payout - } - - // Get normalized reserve cash amount and item quantity - fn get_reserves(self: @Market) -> (u128, u128) { - let reserve_quantity: u128 = (*self.item_quantity).into() * SCALING_FACTOR; - (*self.cash_amount, reserve_quantity) - } - - // Get the liquidity of the market - // Use cubit fixed point math library to compute the square root of the product of the reserves - fn liquidity(self: @Market) -> Fixed { - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert reserve amount to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // L = sqrt(X * Y) - (reserve_amount * reserve_quantity).sqrt() - } - - // Check if the market has liquidity - fn has_liquidity(self: @Market) -> bool { - *self.cash_amount > 0 | *self.item_quantity > 0 - } - - // Given some amount of cash, return the equivalent/optimal quantity of items - // based on the reserves in the market - fn quote_quantity(self: @Market, amount: u128) -> usize { - assert(amount > 0, 'insufficient amount'); - assert(self.has_liquidity(), 'insufficient liquidity'); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert amount to fixed point - let amount = FixedTrait::new_unscaled(amount, false); - - // Convert reserve amount and quantity to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // dy = Y * dx / X - let quantity_optimal = (reserve_quantity * amount) / reserve_amount; - - // Convert from fixed point to usize - quantity_optimal.try_into().unwrap().try_into().unwrap() - } - - // Given some quantity of items, return the equivalent/optimal amount of cash - // based on the reserves in the market - fn quote_amount(self: @Market, quantity: usize) -> u128 { - assert(quantity > 0, 'insufficient quantity'); - assert(self.has_liquidity(), 'insufficient liquidity'); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert reserve amount and quantity to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // Normalize quantity - let quantity: u128 = quantity.into() * SCALING_FACTOR; - - // Convert quantity to fixed point - let quantity = FixedTrait::new_unscaled(quantity, false); - - // dx = X * dy / Y - let amount_optimal = (reserve_amount * quantity) / reserve_quantity; - - // Convert from fixed point to u128 - amount_optimal.try_into().unwrap() - } - - // Inner function to add liquidity to the market, computes the optimal amount and quantity - // - // Arguments: - // - // amount: The amount of cash to add to the market - // quantity: The quantity of items to add to the market - // - // Returns: - // - // (amount, quantity): The amount of cash and quantity of items added to the market - fn add_liquidity_inner(self: @Market, amount: u128, quantity: usize) -> (u128, usize) { - // If there is no liquidity, then the amount and quantity are the optimal - if !self.has_liquidity() { - // Ensure that the amount and quantity are greater than zero - assert(amount > 0, 'insufficient amount'); - assert(quantity > 0, 'insufficient quantity'); - (amount, quantity) - } else { - // Given the amount, get optimal quantity to add to the market - let quantity_optimal = self.quote_quantity(amount); - if quantity_optimal <= quantity { - // Add the given amount and optimal quantity to the market - (amount, quantity_optimal) - } else { - let amount_optimal = self.quote_amount(quantity); - // Ensure that the optimal amount is less than or equal to the given amount - assert(amount_optimal <= amount, 'insufficient amount'); - (amount_optimal, quantity) - } - } - } - - // Add liquidity to the market, mints shares for the given amount of liquidity provided - // - // Arguments: - // - // amount: The amount of cash to add to the market - // quantity: The quantity of items to add to the market - // - // Returns: - // - // (amount, quantity, shares): The amount of cash and quantity of items added to the market and the shares minted - fn add_liquidity(self: @Market, amount: u128, quantity: usize) -> (u128, usize, Fixed) { - // Compute the amount and quantity to add to the market - let (amount, quantity) = self.add_liquidity_inner(amount, quantity); - // Mint shares for the given amount of liquidity provided - let shares = self.mint_shares(amount, quantity); - (amount, quantity, shares) - } - - // Mint shares for the given amount of liquidity provided - fn mint_shares(self: @Market, amount: u128, quantity: usize) -> Fixed { - // If there is no liquidity, then mint total shares - if !self.has_liquidity() { - let quantity: u128 = quantity.into() * SCALING_FACTOR; - (FixedTrait::new_unscaled(amount, false) * FixedTrait::new_unscaled(quantity, false)) - .sqrt() - } else { - // Convert amount to fixed point - let amount = FixedTrait::new_unscaled(amount, false); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, _) = self.get_reserves(); - - // Convert reserve amount to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - - // Get total liquidity - let liquidity = self.liquidity(); - - // Compute the amount of shares to mint - // S = dx * L/X = dy * L/Y - (amount * liquidity) / reserve_amount - } - } - - // Remove liquidity from the market, return the corresponding amount and quantity payout - // - // Arguments: - // - // shares: The amount of liquidity shares to remove from the market - // - // Returns: - // - // (amount, quantity): The amount of cash and quantity of items removed from the market - fn remove_liquidity(self: @Market, shares: Fixed) -> (u128, usize) { - // Ensure that the market has liquidity - let liquidity = self.liquidity(); - assert(shares <= liquidity, 'insufficient liquidity'); - - // Get normalized reserve cash amount and item quantity - let (reserve_amount, reserve_quantity) = self.get_reserves(); - - // Convert reserve amount and quantity to fixed point - let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); - let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); - - // Compute the amount and quantity to remove from the market - // dx = S * X / L - let amount = (shares * reserve_amount) / liquidity; - // dy = S * Y / L - let quantity = (shares * reserve_quantity) / liquidity; - - // Convert amount and quantity both from fixed point to u128 and unscaled usize, respectively - ( - amount.try_into().unwrap(), - (quantity.try_into().unwrap() / SCALING_FACTOR).try_into().unwrap() - ) - } -} - -fn normalize(quantity: usize, market: @Market) -> (u128, u128, u128) { - let quantity: u128 = quantity.into() * SCALING_FACTOR; - let available: u128 = (*market.item_quantity).into() * SCALING_FACTOR; - (quantity, available, *market.cash_amount) -} - - -#[test] -#[should_panic(expected: ('not enough liquidity', ))] -fn test_not_enough_quantity() { - let market = Market { cash_amount: SCALING_FACTOR * 1, item_quantity: 1 }; // pool 1:1 - let cost = market.buy(10); -} - -#[test] -#[available_gas(100000)] -fn test_market_buy() { - let market = Market { cash_amount: SCALING_FACTOR * 1, item_quantity: 10 }; // pool 1:10 - let cost = market.buy(5); - assert(cost == SCALING_FACTOR * 1, 'wrong cost'); -} - -#[test] -#[available_gas(100000)] -fn test_market_sell() { - let market = Market { cash_amount: SCALING_FACTOR * 1, item_quantity: 10 }; // pool 1:10 - let payout = market.sell(5); - assert(payout == 3334, 'wrong payout'); -} - -#[test] -#[available_gas(500000)] -fn test_market_add_liquidity_no_initial() { - // Without initial liquidity - let market = Market { cash_amount: 0, item_quantity: 0 }; - - // Add liquidity - let (amount, quantity) = (SCALING_FACTOR * 5, 5); // pool 1:1 - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); - - // Assert that the amount and quantity added are the same as the given amount and quantity - // and that the liquidity shares minted are the same as the entire liquidity - assert(amount_add == amount, 'wrong cash amount'); - assert(quantity_add == quantity, 'wrong item quantity'); - - // Convert amount and quantity to fixed point - let amount = FixedTrait::new_unscaled(amount, false); - let quantity: u128 = quantity.into() * SCALING_FACTOR; - let quantity = FixedTrait::new_unscaled(quantity, false); - assert(liquidity_add == (amount * quantity).sqrt(), 'wrong liquidity'); -} - -#[test] -#[available_gas(600000)] -fn test_market_add_liquidity_optimal() { - // With initial liquidity - let market = Market { cash_amount: SCALING_FACTOR * 1, item_quantity: 10 }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Add liquidity with the same ratio - let (amount, quantity) = (SCALING_FACTOR * 2, 20); // pool 1:10 - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); - - // Assert - assert(amount_add == amount, 'wrong cash amount'); - assert(quantity_add == quantity, 'wrong item quantity'); - - // Get expected amount and convert to fixed point - let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount, false); - let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; - let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); - - // Compute the expected liquidity shares - let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); - let final_liquidity = initial_liquidity + liquidity_add; - assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); -} - -#[test] -#[available_gas(1000000)] -fn test_market_add_liquidity_not_optimal() { - // With initial liquidity - let market = Market { cash_amount: SCALING_FACTOR * 1, item_quantity: 10 }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Add liquidity without the same ratio - let (amount, quantity) = (SCALING_FACTOR * 2, 10); // pool 1:5 - - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); - - // Assert that the amount added is optimal even though the - // amount originally requested was not - let amount_optimal = SCALING_FACTOR * 1; - assert(amount_add == amount_optimal, 'wrong cash amount'); - assert(quantity_add == quantity, 'wrong item quantity'); - - // Get expected amount and convert to fixed point - let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount_add, false); - let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; - let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); - - // Get expecteed liquidity - let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); - - let final_liquidity = initial_liquidity + liquidity_add; - assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); -} - -#[test] -#[should_panic(expected: ('insufficient amount', ))] -fn test_market_add_liquidity_insufficient_amount() { - let market = Market { cash_amount: SCALING_FACTOR * 1, item_quantity: 10 }; // pool 1:10 - // Adding 20 items requires (SCALING_FACTOR * 2) cash amount to maintain the ratio - // Therefore this should fail - let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(SCALING_FACTOR * 1, 20); -} - - -#[test] -#[available_gas(1000000)] -fn test_market_remove_liquidity() { - // With initial liquidity - let market = Market { cash_amount: SCALING_FACTOR * 2, item_quantity: 20 }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Remove half of the liquidity - let two = FixedTrait::new_unscaled(2, false); - let liquidity_remove = initial_liquidity / two; - - let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); - - // Assert that the amount and quantity removed are half of the initial amount and quantity - assert(amount_remove == SCALING_FACTOR * 1, 'wrong cash amount'); - assert(quantity_remove == 10, 'wrong item quantity'); - - // Get expected amount and convert to fixed point - let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 2 - amount_remove, false); - let expected_quantity: u128 = (20 - quantity_remove).into() * SCALING_FACTOR; - let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); - - // Get expecteed liquidity - let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); - - let final_liquidity = initial_liquidity - liquidity_remove; - assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); -} - -#[test] -#[should_panic(expected: ('insufficient liquidity', ))] -fn test_market_remove_liquidity_no_initial() { - // Without initial liquidity - let market = Market { cash_amount: 0, item_quantity: 0 }; // pool 1:10 - - // Remove liquidity - let one = FixedTrait::new_unscaled(1, false); - - let (amount_remove, quantity_remove) = market.remove_liquidity(one); -} - -#[test] -#[should_panic(expected: ('insufficient liquidity', ))] -fn test_market_remove_liquidity_more_than_available() { - // With initial liquidity - let market = Market { cash_amount: SCALING_FACTOR * 2, item_quantity: 20 }; // pool 1:10 - let initial_liquidity = market.liquidity(); - - // Remove twice of the liquidity - let two = FixedTrait::new_unscaled(2, false); - let liquidity_remove = initial_liquidity * two; - - let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); -} diff --git a/crates/dojo-defi/src/constant_product_market/systems.cairo b/crates/dojo-defi/src/constant_product_market/systems.cairo deleted file mode 100644 index a08150a83b..0000000000 --- a/crates/dojo-defi/src/constant_product_market/systems.cairo +++ /dev/null @@ -1,179 +0,0 @@ -#[system] -mod Buy { - use traits::Into; - use array::ArrayTrait; - use dojo_defi::constant_product_market::components::{Item, Cash, Market, MarketTrait}; - - fn execute(partition: felt252, item_id: felt252, quantity: usize) { - let player: felt252 = starknet::get_caller_address().into(); - - let cash_sk: Query = (partition, (player)).into_partitioned(); - let player_cash = get !(ctx.world, cash_sk, Cash); - - let market_sk: Query = (partition, (item_id)).into_partitioned(); - let market = get !(ctx.world, market_sk, Market); - - let cost = market.buy(quantity); - assert(cost <= player_cash.amount, 'not enough cash'); - - // update market - set !( - ctx.world, - market_sk, - (Market { - cash_amount: market.cash_amount + cost, - item_quantity: market.item_quantity - quantity, - }) - ); - - // update player cash - set !(ctx.world, cash_sk, (Cash { amount: player_cash.amount - cost })); - - // update player item - let item_sk: Query = (partition, (player, item_id)).into_partitioned(); - let item = get !(ctx.world, item_sk, Item); - set !(ctx.world, item_sk, (Item { quantity: item.quantity + quantity })); - } -} - -#[system] -mod Sell { - use traits::Into; - use array::ArrayTrait; - use dojo_defi::constant_product_market::components::{Item, Cash, Market, MarketTrait}; - - fn execute(partition: felt252, item_id: felt252, quantity: usize) { - let player: felt252 = starknet::get_caller_address().into(); - - let item_sk: Query = (partition, (player, item_id)).into_partitioned(); - let item = get !(ctx.world, item_sk, Item); - let player_quantity = item.quantity; - assert(player_quantity >= quantity, 'not enough items'); - - let cash_sk: Query = (partition, (player)).into_partitioned(); - let player_cash = get !(ctx.world, cash_sk, Cash); - - let market_sk: Query = (partition, (item_id)).into_partitioned(); - let market = get !(ctx.world, market_sk, Market); - let payout = market.sell(quantity); - - // update market - set !( - ctx.world, - market_sk, - (Market { - cash_amount: market.cash_amount - payout, - item_quantity: market.item_quantity + quantity, - }) - ); - - // update player cash - set !(ctx.world, cash_sk, (Cash { amount: player_cash.amount + payout })); - - // update player item - set !(ctx.world, item_sk, (Item { quantity: player_quantity - quantity })); - } -} - -#[system] -mod AddLiquidity { - use traits::Into; - use array::ArrayTrait; - use dojo_defi::constant_product_market::components::{ - Item, Cash, Market, Liquidity, MarketTrait - }; - - use cubit::types::fixed::Fixed; - - fn execute(partition: felt252, item_id: felt252, amount: u128, quantity: usize) { - let player: felt252 = starknet::get_caller_address().into(); - - let item_sk: Query = (partition, (player, item_id)).into_partitioned(); - let item = get !(ctx.world, item_sk, Item); - let player_quantity = item.quantity; - assert(player_quantity >= quantity, 'not enough items'); - - let cash_sk: Query = (partition, (player)).into_partitioned(); - let player_cash = get !(ctx.world, cash_sk, Cash); - assert(amount <= player_cash.amount, 'not enough cash'); - - let market_sk: Query = (partition, (item_id)).into_partitioned(); - let market = get !(ctx.world, market_sk, Market); - let (cost_cash, cost_quantity, liquidity_shares) = market.add_liquidity(amount, quantity); - - // update market - set !( - ctx.world, - market_sk, - (Market { - cash_amount: market.cash_amount + cost_cash, - item_quantity: market.item_quantity + cost_quantity - }) - ); - - // update player cash - set !(ctx.world, cash_sk, (Cash { amount: player_cash.amount - cost_cash })); - - // update player item - set !(ctx.world, item_sk, (Item { quantity: player_quantity - cost_quantity })); - - // update player liquidity - let liquidity_sk: Query = (partition, (player, item_id)).into_partitioned(); - let player_liquidity = get !(ctx.world, liquidity_sk, Liquidity); - set !( - ctx.world, - liquidity_sk, - (Liquidity { shares: player_liquidity.shares + liquidity_shares }) - ); - } -} - -#[system] -mod RemoveLiquidity { - use traits::Into; - use array::ArrayTrait; - use dojo_defi::constant_product_market::components::{ - Item, Cash, Market, Liquidity, MarketTrait - }; - - use cubit::types::fixed::Fixed; - use serde::Serde; - - fn execute(partition: felt252, item_id: felt252, shares: Fixed) { - let player: felt252 = starknet::get_caller_address().into(); - - let liquidity_sk: Query = (partition, (player, item_id)).into_partitioned(); - let player_liquidity = get !(ctx.world, liquidity_sk, Liquidity); - assert(player_liquidity.shares >= shares, 'not enough shares'); - - let market_sk: Query = (partition, (item_id)).into_partitioned(); - let market = get !(ctx.world, market_sk, Market); - let (payout_cash, payout_quantity) = market.remove_liquidity(shares); - - // update market - set !( - ctx.world, - market_sk, - (Market { - cash_amount: market.cash_amount - payout_cash, - item_quantity: market.item_quantity - payout_quantity - }) - ); - - // update player cash - let cash_sk: Query = (partition, (player)).into_partitioned(); - let player_cash = get !(ctx.world, cash_sk, Cash); - set !(ctx.world, cash_sk, (Cash { amount: player_cash.amount + payout_cash })); - - // update player item - let item_sk: Query = (partition, (player, item_id)).into_partitioned(); - let item = get !(ctx.world, item_sk, Item); - let player_quantity = item.quantity; - set !(ctx.world, item_sk, (Item { quantity: player_quantity + payout_quantity })); - - // update player liquidity - let liquidity_sk: Query = (partition, (player, item_id)).into_partitioned(); - let player_liquidity = get !(ctx.world, liquidity_sk); - set !(ctx.world, liquidity_sk, (Liquidity { shares: player_liquidity.shares - shares })); - } -} diff --git a/crates/dojo-defi/src/dutch_auction.cairo b/crates/dojo-defi/src/dutch_auction.cairo index 5fef054858..9c0635143f 100644 --- a/crates/dojo-defi/src/dutch_auction.cairo +++ b/crates/dojo-defi/src/dutch_auction.cairo @@ -1,3 +1,4 @@ mod gda; mod vrgda; +mod common; diff --git a/crates/dojo-defi/src/dutch_auction/common.cairo b/crates/dojo-defi/src/dutch_auction/common.cairo new file mode 100644 index 0000000000..f9e0cdd22d --- /dev/null +++ b/crates/dojo-defi/src/dutch_auction/common.cairo @@ -0,0 +1,17 @@ +use cubit::f128::types::fixed::{Fixed, FixedTrait, ONE_u128}; +use dojo_defi::tests::utils::{assert_approx_equal, TOLERANCE}; + +fn to_days_fp(x: Fixed) -> Fixed { + x / FixedTrait::new(86400, false) +} + +fn from_days_fp(x: Fixed) -> Fixed { + x * FixedTrait::new(86400, false) +} + +#[test] +#[available_gas(20000000)] +fn test_days_convertions() { + let days = FixedTrait::new(2, false); + assert_approx_equal(days, to_days_fp(from_days_fp(days)), TOLERANCE * 10); +} diff --git a/crates/dojo-defi/src/dutch_auction/gda.cairo b/crates/dojo-defi/src/dutch_auction/gda.cairo index 20dbe39a19..f2d61b51ec 100644 --- a/crates/dojo-defi/src/dutch_auction/gda.cairo +++ b/crates/dojo-defi/src/dutch_auction/gda.cairo @@ -1,38 +1,67 @@ -use cubit::types::fixed::{Fixed, FixedMul, FixedSub, FixedDiv}; +use cubit::f128::math::core::{exp, pow}; +use cubit::f128::types::fixed::{Fixed, FixedTrait}; -#[derive(Component, Drop)] -struct GradualDutchAuction { +use debug::PrintTrait; + +/// A Gradual Dutch Auction represented using discrete time steps. +/// The purchase price for a given quantity is calculated based on +/// the initial price, scale factor, decay constant, and the time since +/// the auction has started. +#[derive(Copy, Drop, Serde, starknet::Storage)] +struct DiscreteGDA { + sold: Fixed, initial_price: Fixed, scale_factor: Fixed, decay_constant: Fixed, - auction_start_time: Fixed, } -trait GradualDutchAuctionTrait { - fn purchase_price(self: @Market, quantity: u128, existing: u128, current_time: u128) -> Fixed; +#[generate_trait] +impl DiscreteGDAImpl of DiscreteGDATrait { + /// Calculates the purchase price for a given quantity of the item at a specific time. + /// + /// # Arguments + /// + /// * `time_since_start`: Time since the start of the auction in days. + /// * `quantity`: Quantity of the item being purchased. + /// + /// # Returns + /// + /// * A `Fixed` representing the purchase price. + fn purchase_price(self: @DiscreteGDA, time_since_start: Fixed, quantity: Fixed) -> Fixed { + let num1 = *self.initial_price * pow(*self.scale_factor, *self.sold); + let num2 = pow(*self.scale_factor, quantity) - FixedTrait::ONE(); + let den1 = exp(*self.decay_constant * time_since_start); + let den2 = *self.scale_factor - FixedTrait::ONE(); + (num1 * num2) / (den1 * den2) + } } -impl GradualDutchAuctionImpl of GradualDutchAuctionTrait { - fn purchase_price(self: @Market, quantity: u128, existing: u128, current_time: u128) -> Fixed { - let quantity_fp = Fixed::new(quantity, true); - let existing_fp = Fixed::new(existing, true); - let current_time_fp = Fixed::new(current_time, true); - - let num1_pow = Fixed::pow(*self.scale_factor, existing_fp); - let num1 = FixedMul::mul(*self.initial_price, num1_pow); - - let num2_pow = Fixed::pow(*self.scale_factor, quantity_fp); - let num2 = FixedMul::mul(num2_pow, Fixed::new(1, true)); - - let den1_mul = FixedMul::mul(*self.decay_constant, *self.auction_start_time); - let den1 = Fixed::exp(den1_mul); - let den2 = FixedSub::sub(*self.scale_factor, Fixed::new(1, true)); - - let mul_num2 = FixedMul::mul(num1, num2); - let mul_num3 = FixedMul::mul(den1, den2); +/// A Gradual Dutch Auction represented using continuous time steps. +/// The purchase price is calculated based on the initial price, +/// emission rate, decay constant, and the time since the last purchase in days. +#[derive(Copy, Drop, Serde, starknet::Storage)] +struct ContinuousGDA { + initial_price: Fixed, + emission_rate: Fixed, + decay_constant: Fixed, +} - let total_cost = FixedDiv::div(mul_num2, mul_num3); - total_cost +#[generate_trait] +impl ContinuousGDAImpl of ContinuousGDATrait { + /// Calculates the purchase price for a given quantity of the item at a specific time. + /// + /// # Arguments + /// + /// * `time_since_last`: Time since the last purchase in the auction in days. + /// * `quantity`: Quantity of the item being purchased. + /// + /// # Returns + /// + /// * A `Fixed` representing the purchase price. + fn purchase_price(self: @ContinuousGDA, time_since_last: Fixed, quantity: Fixed) -> Fixed { + let num1 = *self.initial_price / *self.decay_constant; + let num2 = exp((*self.decay_constant * quantity) / *self.emission_rate) - FixedTrait::ONE(); + let den = exp(*self.decay_constant * time_since_last); + (num1 * num2) / den } } - diff --git a/crates/dojo-defi/src/dutch_auction/vrgda.cairo b/crates/dojo-defi/src/dutch_auction/vrgda.cairo index be2548268f..3790bfc64a 100644 --- a/crates/dojo-defi/src/dutch_auction/vrgda.cairo +++ b/crates/dojo-defi/src/dutch_auction/vrgda.cairo @@ -1,35 +1,121 @@ -use cubit::types::fixed::{Fixed, FixedAdd, FixedMul, FixedSub, FixedDiv}; +use cubit::f128::math::core::{ln, abs, exp}; +use cubit::f128::types::fixed::{Fixed, FixedTrait}; -#[derive(Component)] -#[derive(Drop)] -struct VariableGradualDutchAuction { +/// A Linear Variable Rate Gradual Dutch Auction (VRGDA) struct. +/// Represents an auction where the price decays linearly based on the target price, +/// decay constant, and per-time-unit rate. +#[derive(Copy, Drop, Serde, starknet::Storage)] +struct LinearVRGDA { target_price: Fixed, - scale_factor: Fixed, decay_constant: Fixed, per_time_unit: Fixed, } -trait VrgdaTrait { - fn get_target_sale_time(sold: u128) -> Fixed; - fn vrgda_price(time_start: u128, sold: u128) -> Fixed; -} +#[generate_trait] +impl LinearVRGDAImpl of LinearVRGDATrait { + /// Calculates the target sale time based on the quantity sold. + /// + /// # Arguments + /// + /// * `sold`: Quantity sold. + /// + /// # Returns + /// + /// * A `Fixed` representing the target sale time. + fn get_target_sale_time(self: @LinearVRGDA, sold: Fixed) -> Fixed { + sold / *self.per_time_unit + } -impl VrgdaImpl of VrgdaTrait { - fn get_target_sale_time(self: @Market, sold: u128) -> Fixed { - let sold_fp = Fixed::new(sold, false); + /// Calculates the VRGDA price at a specific time since the auction started. + /// + /// # Arguments + /// + /// * `time_since_start`: Time since the auction started. + /// * `sold`: Quantity sold. + /// + /// # Returns + /// + /// * A `Fixed` representing the price. + fn get_vrgda_price(self: @LinearVRGDA, time_since_start: Fixed, sold: Fixed) -> Fixed { + *self.target_price + * exp( + *self.decay_constant + * (time_since_start + - self.get_target_sale_time(sold + FixedTrait::new(1, false))) + ) + } - FixedDiv::div(sold_fp, *self.per_time_unit) + fn get_reverse_vrgda_price(self: @LinearVRGDA, time_since_start: Fixed, sold: Fixed) -> Fixed { + *self.target_price + * exp( + (*self.decay_constant * FixedTrait::new(1, true)) + * (time_since_start + - self.get_target_sale_time(sold + FixedTrait::new(1, false))) + ) } +} + + +#[derive(Copy, Drop, Serde, starknet::Storage)] +struct LogisticVRGDA { + target_price: Fixed, + decay_constant: Fixed, + max_sellable: Fixed, + time_scale: Fixed, +} - fn vrgda_price(time_start: u128, sold: u128) -> Fixed { - time_start_fp = Fixed::new(time_start, false); - sold_fp = Fixed::new(sold, false); +// A Logistic Variable Rate Gradual Dutch Auction (VRGDA) struct. +/// Represents an auction where the price decays according to a logistic function, +/// based on the target price, decay constant, max sellable quantity, and time scale. +#[generate_trait] +impl LogisticVRGDAImpl of LogisticVRGDATrait { + /// Calculates the target sale time using a logistic function based on the quantity sold. + /// + /// # Arguments + /// + /// * `sold`: Quantity sold. + /// + /// # Returns + /// + /// * A `Fixed` representing the target sale time. + fn get_target_sale_time(self: @LogisticVRGDA, sold: Fixed) -> Fixed { + let logistic_limit = *self.max_sellable + FixedTrait::ONE(); + let logistic_limit_double = logistic_limit * FixedTrait::new_unscaled(2, false); + abs( + ln(logistic_limit_double / (sold + logistic_limit) - FixedTrait::ONE()) + / *self.time_scale + ) + } - num1 = FixedAdd::add(sold_fp, Fixed::new(1, false)); - num2 = FixedSub::sub(time_start_fp, num1); - num3 = FixedMul::mul(*self.decay_constant, num2); - num4 = Fixed::exp(num3); + /// Calculates the VRGDA price at a specific time since the auction started, + /// using the logistic function. + /// + /// # Arguments + /// + /// * `time_since_start`: Time since the auction started. + /// * `sold`: Quantity sold. + /// + /// # Returns + /// + /// * A `Fixed` representing the price. + fn get_vrgda_price(self: @LogisticVRGDA, time_since_start: Fixed, sold: Fixed) -> Fixed { + *self.target_price + * exp( + *self.decay_constant + * (time_since_start + - self.get_target_sale_time(sold + FixedTrait::new(1, false))) + ) + } - FixedMul::mul(*self.target_price, num4) + fn get_reverse_vrgda_price( + self: @LogisticVRGDA, time_since_start: Fixed, sold: Fixed + ) -> Fixed { + *self.target_price + * exp( + (*self.decay_constant * FixedTrait::new(1, true)) + * (time_since_start + - self.get_target_sale_time(sold + FixedTrait::new(1, false))) + ) } } + diff --git a/crates/dojo-defi/src/lib.cairo b/crates/dojo-defi/src/lib.cairo index 42f9a7d87d..b764885332 100644 --- a/crates/dojo-defi/src/lib.cairo +++ b/crates/dojo-defi/src/lib.cairo @@ -1,3 +1,4 @@ -mod constant_product_market; +mod market; mod dutch_auction; +mod tests; diff --git a/crates/dojo-defi/src/market.cairo b/crates/dojo-defi/src/market.cairo new file mode 100644 index 0000000000..cb5ccb799c --- /dev/null +++ b/crates/dojo-defi/src/market.cairo @@ -0,0 +1,3 @@ +mod models; +mod systems; +mod constant_product_market; diff --git a/crates/dojo-defi/src/market/constant_product_market.cairo b/crates/dojo-defi/src/market/constant_product_market.cairo new file mode 100644 index 0000000000..1fd4839804 --- /dev/null +++ b/crates/dojo-defi/src/market/constant_product_market.cairo @@ -0,0 +1,211 @@ +use starknet::ContractAddress; +use dojo_defi::market::models::Market; +use cubit::f128::types::fixed::{Fixed, FixedTrait}; + +const SCALING_FACTOR: u128 = 10000; + +#[generate_trait] +impl MarketImpl of MarketTrait { + fn buy(self: @Market, quantity: u128) -> u128 { + assert(quantity < *self.item_quantity, 'not enough liquidity'); + let (quantity, available, cash) = normalize(quantity, self); + let k = cash * available; + let cost = (k / (available - quantity)) - cash; + cost + } + + fn sell(self: @Market, quantity: u128) -> u128 { + let (quantity, available, cash) = normalize(quantity, self); + let k = cash * available; + let payout = cash - (k / (available + quantity)); + payout + } + + // Get normalized reserve cash amount and item quantity + fn get_reserves(self: @Market) -> (u128, u128) { + let reserve_quantity: u128 = (*self.item_quantity).into() * SCALING_FACTOR; + (*self.cash_amount, reserve_quantity) + } + + // Get the liquidity of the market + // Use cubit fixed point math library to compute the square root of the product of the reserves + fn liquidity(self: @Market) -> Fixed { + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert reserve amount to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // L = sqrt(X * Y) + (reserve_amount * reserve_quantity).sqrt() + } + + // Check if the market has liquidity + fn has_liquidity(self: @Market) -> bool { + *self.cash_amount > 0 || *self.item_quantity > 0 + } + + // Given some amount of cash, return the equivalent/optimal quantity of items + // based on the reserves in the market + fn quote_quantity(self: @Market, amount: u128) -> u128 { + assert(amount > 0, 'insufficient amount'); + assert(self.has_liquidity(), 'insufficient liquidity'); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert amount to fixed point + let amount = FixedTrait::new_unscaled(amount, false); + + // Convert reserve amount and quantity to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // dy = Y * dx / X + let quantity_optimal = (reserve_quantity * amount) / reserve_amount; + + // Convert from fixed point to u128 + let res: u128 = quantity_optimal.try_into().unwrap(); + res + } + + // Given some quantity of items, return the equivalent/optimal amount of cash + // based on the reserves in the market + fn quote_amount(self: @Market, quantity: u128) -> u128 { + assert(quantity > 0, 'insufficient quantity'); + assert(self.has_liquidity(), 'insufficient liquidity'); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert reserve amount and quantity to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // Normalize quantity + let quantity: u128 = quantity.into() * SCALING_FACTOR; + + // Convert quantity to fixed point + let quantity = FixedTrait::new_unscaled(quantity, false); + + // dx = X * dy / Y + let amount_optimal = (reserve_amount * quantity) / reserve_quantity; + + // Convert from fixed point to u128 + amount_optimal.try_into().unwrap() + } + + // Inner function to add liquidity to the market, computes the optimal amount and quantity + // + // Arguments: + // + // amount: The amount of cash to add to the market + // quantity: The quantity of items to add to the market + // + // Returns: + // + // (amount, quantity): The amount of cash and quantity of items added to the market + fn add_liquidity_inner(self: @Market, amount: u128, quantity: u128) -> (u128, u128) { + // If there is no liquidity, then the amount and quantity are the optimal + if !self.has_liquidity() { + // Ensure that the amount and quantity are greater than zero + assert(amount > 0, 'insufficient amount'); + assert(quantity > 0, 'insufficient quantity'); + (amount, quantity) + } else { + // Given the amount, get optimal quantity to add to the market + let quantity_optimal = self.quote_quantity(amount); + if quantity_optimal <= quantity { + // Add the given amount and optimal quantity to the market + (amount, quantity_optimal) + } else { + let amount_optimal = self.quote_amount(quantity); + // Ensure that the optimal amount is less than or equal to the given amount + assert(amount_optimal <= amount, 'insufficient amount'); + (amount_optimal, quantity) + } + } + } + + // Add liquidity to the market, mints shares for the given amount of liquidity provided + // + // Arguments: + // + // amount: The amount of cash to add to the market + // quantity: The quantity of items to add to the market + // + // Returns: + // + // (amount, quantity, shares): The amount of cash and quantity of items added to the market and the shares minted + fn add_liquidity(self: @Market, amount: u128, quantity: u128) -> (u128, u128, Fixed) { + // Compute the amount and quantity to add to the market + let (amount, quantity) = self.add_liquidity_inner(amount, quantity); + // Mint shares for the given amount of liquidity provided + let shares = self.mint_shares(amount, quantity); + (amount, quantity, shares) + } + + // Mint shares for the given amount of liquidity provided + fn mint_shares(self: @Market, amount: u128, quantity: u128) -> Fixed { + // If there is no liquidity, then mint total shares + if !self.has_liquidity() { + let quantity: u128 = quantity.into() * SCALING_FACTOR; + (FixedTrait::new_unscaled(amount, false) * FixedTrait::new_unscaled(quantity, false)) + .sqrt() + } else { + // Convert amount to fixed point + let amount = FixedTrait::new_unscaled(amount, false); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, _) = self.get_reserves(); + + // Convert reserve amount to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + + // Get total liquidity + let liquidity = self.liquidity(); + + // Compute the amount of shares to mint + // S = dx * L/X = dy * L/Y + (amount * liquidity) / reserve_amount + } + } + + // Remove liquidity from the market, return the corresponding amount and quantity payout + // + // Arguments: + // + // shares: The amount of liquidity shares to remove from the market + // + // Returns: + // + // (amount, quantity): The amount of cash and quantity of items removed from the market + fn remove_liquidity(self: @Market, shares: Fixed) -> (u128, u128) { + // Ensure that the market has liquidity + let liquidity = self.liquidity(); + assert(shares <= liquidity, 'insufficient liquidity'); + + // Get normalized reserve cash amount and item quantity + let (reserve_amount, reserve_quantity) = self.get_reserves(); + + // Convert reserve amount and quantity to fixed point + let reserve_amount = FixedTrait::new_unscaled(reserve_amount, false); + let reserve_quantity = FixedTrait::new_unscaled(reserve_quantity, false); + + // Compute the amount and quantity to remove from the market + // dx = S * X / L + let amount = (shares * reserve_amount) / liquidity; + // dy = S * Y / L + let quantity = (shares * reserve_quantity) / liquidity; + + // Convert amount and quantity both from fixed point to u128 and unscaled u128, respectively + (amount.try_into().unwrap(), quantity.try_into().unwrap() / SCALING_FACTOR) + } +} + +fn normalize(quantity: u128, market: @Market) -> (u128, u128, u128) { + let quantity: u128 = quantity.into() * SCALING_FACTOR; + let available: u128 = (*market.item_quantity).into() * SCALING_FACTOR; + (quantity, available, *market.cash_amount) +} diff --git a/crates/dojo-defi/src/market/models.cairo b/crates/dojo-defi/src/market/models.cairo new file mode 100644 index 0000000000..c7da959dfe --- /dev/null +++ b/crates/dojo-defi/src/market/models.cairo @@ -0,0 +1,58 @@ +use starknet::ContractAddress; +use dojo::model::StorageIntrospection; + +// Cubit fixed point math library +use cubit::f128::types::fixed::Fixed; + +const SCALING_FACTOR: u128 = 10000; + +impl SchemaIntrospectionFixed of SchemaIntrospection { + #[inline(always)] + fn unpacked_size() -> usize { + 1 + } + + #[inline(always)] + fn packed_size() -> usize { + 129 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(128); + layout.append(1); + } +} + +#[derive(Model, Copy, Drop, Serde)] +struct Cash { + #[key] + player: ContractAddress, + amount: u128, +} + +#[derive(Model, Copy, Drop, Serde)] +struct Item { + #[key] + player: ContractAddress, + #[key] + item_id: u32, + quantity: u128, +} + +#[derive(Model, Copy, Drop, Serde)] +struct Liquidity { + #[key] + player: ContractAddress, + #[key] + item_id: u32, + shares: Fixed, +} + +#[derive(Model, Copy, Drop, Serde)] +struct Market { + #[key] + item_id: u32, + cash_amount: u128, + item_quantity: u128, +} diff --git a/crates/dojo-defi/src/market/systems.cairo b/crates/dojo-defi/src/market/systems.cairo new file mode 100644 index 0000000000..078bd5cec1 --- /dev/null +++ b/crates/dojo-defi/src/market/systems.cairo @@ -0,0 +1,176 @@ +#[system] +mod Buy { + use dojo_defi::market::models::{Item, Cash, Market}; + use dojo_defi::market::constant_product_market::MarketTrait; + use dojo::world::Context; + + fn execute(ctx: Context, item_id: u32, quantity: u128) { + let player = starknet::get_caller_address(); + + let player_cash = get!(ctx.world, (player), Cash); + + let market = get!(ctx.world, (item_id), Market); + + let cost = market.buy(quantity); + assert(cost <= player_cash.amount, 'not enough cash'); + + // update market + set!( + ctx.world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount + cost, + item_quantity: market.item_quantity - quantity, + }) + ); + + // update player cash + set!(ctx.world, (Cash { player: player, amount: player_cash.amount - cost })); + + // update player item + let item = get!(ctx.world, (player, item_id), Item); + set!( + ctx.world, + (Item { player: player, item_id: item_id, quantity: item.quantity + quantity }) + ); + } +} + +#[system] +mod Sell { + use dojo_defi::market::models::{Item, Cash, Market}; + use dojo_defi::market::constant_product_market::MarketTrait; + use dojo::world::Context; + + fn execute(ctx: Context, item_id: u32, quantity: u128) { + let player = starknet::get_caller_address(); + + let item = get!(ctx.world, (player, item_id), Item); + let player_quantity = item.quantity; + assert(player_quantity >= quantity, 'not enough items'); + + let player_cash = get!(ctx.world, (player), Cash); + + let market = get!(ctx.world, (item_id), Market); + let payout = market.sell(quantity); + + // update market + set!( + ctx.world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount - payout, + item_quantity: market.item_quantity + quantity, + }) + ); + + // update player cash + set!(ctx.world, (Cash { player: player, amount: player_cash.amount + payout })); + + // update player item + set!( + ctx.world, + (Item { player: player, item_id: item_id, quantity: player_quantity - quantity }) + ); + } +} + +#[system] +mod AddLiquidity { + use dojo_defi::market::models::{Item, Cash, Market, Liquidity}; + use dojo_defi::market::constant_product_market::MarketTrait; + use dojo::world::Context; + + fn execute(ctx: Context, item_id: u32, amount: u128, quantity: u128) { + let player = starknet::get_caller_address(); + + let item = get!(ctx.world, (player, item_id), Item); + let player_quantity = item.quantity; + assert(player_quantity >= quantity, 'not enough items'); + + let player_cash = get!(ctx.world, (player), Cash); + assert(amount <= player_cash.amount, 'not enough cash'); + + let market = get!(ctx.world, (item_id), Market); + let (cost_cash, cost_quantity, liquidity_shares) = market.add_liquidity(amount, quantity); + + // update market + set!( + ctx.world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount + cost_cash, + item_quantity: market.item_quantity + cost_quantity + }) + ); + + // update player cash + set!(ctx.world, (Cash { player: player, amount: player_cash.amount - cost_cash })); + + // update player item + set!( + ctx.world, + (Item { player: player, item_id: item_id, quantity: player_quantity - cost_quantity }) + ); + + // update player liquidity + let player_liquidity = get!(ctx.world, (player, item_id), Liquidity); + set!( + ctx.world, + (Liquidity { + player: player, item_id: item_id, shares: player_liquidity.shares + liquidity_shares + }) + ); + } +} + +#[system] +mod RemoveLiquidity { + use dojo_defi::market::models::{Item, Cash, Market, Liquidity}; + use dojo_defi::market::constant_product_market::MarketTrait; + use dojo::world::Context; + + use cubit::f128::types::fixed::Fixed; + + fn execute(ctx: Context, item_id: u32, shares: Fixed) { + let player = starknet::get_caller_address(); + + let player_liquidity = get!(ctx.world, (player, item_id), Liquidity); + assert(player_liquidity.shares >= shares, 'not enough shares'); + + let market = get!(ctx.world, (item_id), Market); + let (payout_cash, payout_quantity) = market.remove_liquidity(shares); + + // update market + set!( + ctx.world, + (Market { + item_id: item_id, + cash_amount: market.cash_amount - payout_cash, + item_quantity: market.item_quantity - payout_quantity + }) + ); + + // update player cash + let player_cash = get!(ctx.world, (player), Cash); + set!(ctx.world, (Cash { player: player, amount: player_cash.amount + payout_cash })); + + // update player item + let item = get!(ctx.world, (player, item_id), Item); + let player_quantity = item.quantity; + set!( + ctx.world, + (Item { player: player, item_id: item_id, quantity: player_quantity + payout_quantity }) + ); + + // update player liquidity + let player_liquidity = get!(ctx.world, (player, item_id), Liquidity); + set!( + ctx.world, + (Liquidity { + player: player, item_id: item_id, shares: player_liquidity.shares - shares + }) + ); + } +} + diff --git a/crates/dojo-defi/src/tests.cairo b/crates/dojo-defi/src/tests.cairo new file mode 100644 index 0000000000..178dfdfa03 --- /dev/null +++ b/crates/dojo-defi/src/tests.cairo @@ -0,0 +1,11 @@ +#[cfg(test)] +mod discrete_gda_test; +#[cfg(test)] +mod continuous_gda_test; +#[cfg(test)] +mod linear_vrgda_test; +#[cfg(test)] +mod logistic_vrgda_test; +#[cfg(test)] +mod constant_product_market_tests; +mod utils; diff --git a/crates/dojo-defi/src/tests/constant_product_market_tests.cairo b/crates/dojo-defi/src/tests/constant_product_market_tests.cairo new file mode 100644 index 0000000000..4d51200d4d --- /dev/null +++ b/crates/dojo-defi/src/tests/constant_product_market_tests.cairo @@ -0,0 +1,188 @@ +use traits::Into; + +use dojo_defi::market::models::Market; +use dojo_defi::market::constant_product_market::{MarketTrait, SCALING_FACTOR}; +use dojo_defi::tests::utils::{TOLERANCE, assert_approx_equal}; + +use cubit::f128::types::FixedTrait; + +#[test] +#[should_panic(expected: ('not enough liquidity',))] +fn test_not_enough_quantity() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 1 + }; // pool 1:1 + let cost = market.buy(10); +} + +#[test] +#[available_gas(100000)] +fn test_market_buy() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let cost = market.buy(5); + assert(cost == SCALING_FACTOR * 1, 'wrong cost'); +} + +#[test] +#[available_gas(100000)] +fn test_market_sell() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let payout = market.sell(5); + assert(payout == 3334, 'wrong payout'); +} + +#[test] +#[available_gas(500000)] +fn test_market_add_liquidity_no_initial() { + // Without initial liquidity + let market = Market { item_id: 1, cash_amount: 0, item_quantity: 0 }; + + // Add liquidity + let (amount, quantity) = (SCALING_FACTOR * 5, 5); // pool 1:1 + let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); + + // Assert that the amount and quantity added are the same as the given amount and quantity + // and that the liquidity shares minted are the same as the entire liquidity + assert(amount_add == amount, 'wrong cash amount'); + assert(quantity_add == quantity, 'wrong item quantity'); + + // Convert amount and quantity to fixed point + let amount = FixedTrait::new_unscaled(amount, false); + let quantity: u128 = quantity.into() * SCALING_FACTOR; + let quantity = FixedTrait::new_unscaled(quantity, false); + assert(liquidity_add == (amount * quantity).sqrt(), 'wrong liquidity'); +} + +#[test] +#[available_gas(600000)] +fn test_market_add_liquidity_optimal() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Add liquidity with the same ratio + let (amount, quantity) = (SCALING_FACTOR * 2, 20); // pool 1:10 + let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); + + // Assert + assert(amount_add == amount, 'wrong cash amount'); + assert(quantity_add == quantity, 'wrong item quantity'); + + // Get expected amount and convert to fixed point + let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount, false); + let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; + let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); + + // Compute the expected liquidity shares + let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); + let final_liquidity = initial_liquidity + liquidity_add; + assert_approx_equal(expected_liquidity, final_liquidity, TOLERANCE); +} + +#[test] +#[available_gas(1000000)] +fn test_market_add_liquidity_not_optimal() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Add liquidity without the same ratio + let (amount, quantity) = (SCALING_FACTOR * 2, 10); // pool 1:5 + + let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(amount, quantity); + + // Assert that the amount added is optimal even though the + // amount originally requested was not + let amount_optimal = SCALING_FACTOR * 1; + assert(amount_add == amount_optimal, 'wrong cash amount'); + assert(quantity_add == quantity, 'wrong item quantity'); + + // Get expected amount and convert to fixed point + let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 1 + amount_add, false); + let expected_quantity: u128 = (10 + quantity).into() * SCALING_FACTOR; + let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); + + // Get expecteed liquidity + let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); + + let final_liquidity = initial_liquidity + liquidity_add; +// assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); +} + +#[test] +#[should_panic(expected: ('insufficient amount',))] +fn test_market_add_liquidity_insufficient_amount() { + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 1, item_quantity: 10 + }; // pool 1:10 + // Adding 20 items requires (SCALING_FACTOR * 2) cash amount to maintain the ratio + // Therefore this should fail + let (amount_add, quantity_add, liquidity_add) = market.add_liquidity(SCALING_FACTOR * 1, 20); +} + +#[test] +#[available_gas(1000000)] +fn test_market_remove_liquidity() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 2, item_quantity: 20 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Remove half of the liquidity + let two = FixedTrait::new_unscaled(2, false); + let liquidity_remove = initial_liquidity / two; + + let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); + + // Assert that the amount and quantity removed are half of the initial amount and quantity + assert(amount_remove == SCALING_FACTOR * 1, 'wrong cash amount'); + assert(quantity_remove == 10, 'wrong item quantity'); + + // Get expected amount and convert to fixed point + let expected_amount = FixedTrait::new_unscaled(SCALING_FACTOR * 2 - amount_remove, false); + let expected_quantity: u128 = (20 - quantity_remove).into() * SCALING_FACTOR; + let expected_quantity = FixedTrait::new_unscaled(expected_quantity, false); + + // Get expecteed liquidity + let expected_liquidity = FixedTrait::sqrt(expected_amount * expected_quantity); + + let final_liquidity = initial_liquidity - liquidity_remove; +// assert_precise(expected_liquidity, final_liquidity.into(), 'wrong liquidity', Option::None(())); +} + +#[test] +#[should_panic(expected: ('insufficient liquidity',))] +fn test_market_remove_liquidity_no_initial() { + // Without initial liquidity + let market = Market { item_id: 1, cash_amount: 0, item_quantity: 0 }; // pool 1:10 + + // Remove liquidity + let one = FixedTrait::new_unscaled(1, false); + + let (amount_remove, quantity_remove) = market.remove_liquidity(one); +} + +#[test] +#[should_panic(expected: ('insufficient liquidity',))] +fn test_market_remove_liquidity_more_than_available() { + // With initial liquidity + let market = Market { + item_id: 1, cash_amount: SCALING_FACTOR * 2, item_quantity: 20 + }; // pool 1:10 + let initial_liquidity = market.liquidity(); + + // Remove twice of the liquidity + let two = FixedTrait::new_unscaled(2, false); + let liquidity_remove = initial_liquidity * two; + + let (amount_remove, quantity_remove) = market.remove_liquidity(liquidity_remove); +} diff --git a/crates/dojo-defi/src/tests/continuous_gda_test.cairo b/crates/dojo-defi/src/tests/continuous_gda_test.cairo new file mode 100644 index 0000000000..0e404c11bb --- /dev/null +++ b/crates/dojo-defi/src/tests/continuous_gda_test.cairo @@ -0,0 +1,67 @@ +use cubit::f128::types::fixed::{Fixed, FixedTrait}; + +use dojo_defi::dutch_auction::gda::{ContinuousGDA, ContinuousGDATrait}; +use dojo_defi::tests::utils::{assert_approx_equal, TOLERANCE}; + +// ipynb with calculations at https://colab.research.google.com/drive/14elIFRXdG3_gyiI43tP47lUC_aClDHfB?usp=sharing +#[test] +#[available_gas(2000000)] +fn test_price_1() { + let auction = ContinuousGDA { + initial_price: FixedTrait::new_unscaled(1000, false), + emission_rate: FixedTrait::ONE(), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let expected = FixedTrait::new(22128445337405634000000, false); + let time_since_last = FixedTrait::new_unscaled(10, false); + let quantity = FixedTrait::new_unscaled(9, false); + let price: Fixed = auction.purchase_price(time_since_last, quantity); + assert_approx_equal(price, expected, TOLERANCE) +} + + +#[test] +#[available_gas(2000000)] +fn test_price_2() { + let auction = ContinuousGDA { + initial_price: FixedTrait::new_unscaled(1000, false), + emission_rate: FixedTrait::ONE(), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let expected = FixedTrait::new(89774852279643700000, false); + let time_since_last = FixedTrait::new_unscaled(20, false); + let quantity = FixedTrait::new_unscaled(8, false); + let price: Fixed = auction.purchase_price(time_since_last, quantity); + assert_approx_equal(price, expected, TOLERANCE) +} + +#[test] +#[available_gas(2000000)] +fn test_price_3() { + let auction = ContinuousGDA { + initial_price: FixedTrait::new_unscaled(1000, false), + emission_rate: FixedTrait::ONE(), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let expected = FixedTrait::new(20393925850936156000, false); + let time_since_last = FixedTrait::new_unscaled(30, false); + let quantity = FixedTrait::new_unscaled(15, false); + let price: Fixed = auction.purchase_price(time_since_last, quantity); + assert_approx_equal(price, expected, TOLERANCE) +} + +#[test] +#[available_gas(2000000)] +fn test_price_4() { + let auction = ContinuousGDA { + initial_price: FixedTrait::new_unscaled(1000, false), + emission_rate: FixedTrait::ONE(), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let expected = FixedTrait::new(3028401847768577000000, false); + let time_since_last = FixedTrait::new_unscaled(40, false); + let quantity = FixedTrait::new_unscaled(35, false); + let price: Fixed = auction.purchase_price(time_since_last, quantity); + assert_approx_equal(price, expected, TOLERANCE) +} + diff --git a/crates/dojo-defi/src/tests/discrete_gda_test.cairo b/crates/dojo-defi/src/tests/discrete_gda_test.cairo new file mode 100644 index 0000000000..ac0a52dc73 --- /dev/null +++ b/crates/dojo-defi/src/tests/discrete_gda_test.cairo @@ -0,0 +1,79 @@ +use cubit::f128::types::fixed::{Fixed, FixedTrait}; + +use dojo_defi::dutch_auction::gda::{DiscreteGDA, DiscreteGDATrait}; +use dojo_defi::tests::utils::{assert_approx_equal, TOLERANCE}; + +#[test] +#[available_gas(2000000)] +fn test_initial_price() { + let auction = DiscreteGDA { + sold: FixedTrait::new_unscaled(0, false), + initial_price: FixedTrait::new_unscaled(1000, false), + scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let price = auction.purchase_price(FixedTrait::ZERO(), FixedTrait::ONE()); + assert_approx_equal(price, auction.initial_price, TOLERANCE) +} + +// ipynb with calculations at https://colab.research.google.com/drive/14elIFRXdG3_gyiI43tP47lUC_aClDHfB?usp=sharing +#[test] +#[available_gas(2000000)] +fn test_price_1() { + let auction = DiscreteGDA { + sold: FixedTrait::new_unscaled(1, false), + initial_price: FixedTrait::new_unscaled(1000, false), + scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let expected = FixedTrait::new(1856620062541316600000, false); + let price = auction + .purchase_price(FixedTrait::new_unscaled(10, false), FixedTrait::new_unscaled(9, false), ); + assert_approx_equal(price, expected, TOLERANCE) +} + +#[test] +#[available_gas(2000000)] +fn test_price_2() { + let auction = DiscreteGDA { + sold: FixedTrait::new_unscaled(2, false), + initial_price: FixedTrait::new_unscaled(1000, false), + scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), + decay_constant: FixedTrait::new(1, false) / FixedTrait::new(2, false), + }; + let expected = FixedTrait::new(2042282068795448600000, false); + let price = auction + .purchase_price(FixedTrait::new_unscaled(10, false), FixedTrait::new_unscaled(9, false), ); + assert_approx_equal(price, expected, TOLERANCE) +} + +#[test] +#[available_gas(2000000)] +fn test_price_3() { + let auction = DiscreteGDA { + sold: FixedTrait::new_unscaled(4, false), + initial_price: FixedTrait::new_unscaled(1000, false), + scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let expected = FixedTrait::new(2471161303242493000000, false); + let price = auction + .purchase_price(FixedTrait::new_unscaled(10, false), FixedTrait::new_unscaled(9, false), ); + assert_approx_equal(price, expected, TOLERANCE) +} + +#[test] +#[available_gas(2000000)] +fn test_price_4() { + let auction = DiscreteGDA { + sold: FixedTrait::new_unscaled(20, false), + initial_price: FixedTrait::new_unscaled(1000, false), + scale_factor: FixedTrait::new_unscaled(11, false) / FixedTrait::new_unscaled(10, false), + decay_constant: FixedTrait::new_unscaled(1, false) / FixedTrait::new_unscaled(2, false), + }; + let expected = FixedTrait::new(291, false); + let price = auction + .purchase_price(FixedTrait::new_unscaled(85, false), FixedTrait::new_unscaled(1, false), ); + assert_approx_equal(price, expected, TOLERANCE) +} + diff --git a/crates/dojo-defi/src/tests/linear_vrgda_test.cairo b/crates/dojo-defi/src/tests/linear_vrgda_test.cairo new file mode 100644 index 0000000000..3a532d06e6 --- /dev/null +++ b/crates/dojo-defi/src/tests/linear_vrgda_test.cairo @@ -0,0 +1,54 @@ +use cubit::f128::types::fixed::{Fixed, FixedTrait}; + +use dojo_defi::dutch_auction::common::{to_days_fp, from_days_fp}; +use dojo_defi::dutch_auction::vrgda::{LinearVRGDA, LinearVRGDATrait}; +use dojo_defi::tests::utils::assert_rel_approx_eq; + +const _69_42: u128 = 1280572973596917000000; +const _0_31: u128 = 5718490662849961000; +const DELTA_0_0005: u128 = 9223372036854776; +const DELTA_0_02: u128 = 368934881474191000; +const DELTA: u128 = 184467440737095; + +#[test] +#[available_gas(2000000)] +fn test_target_price() { + let auction = LinearVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + per_time_unit: FixedTrait::new_unscaled(2, false), + }; + let time = from_days_fp(auction.get_target_sale_time(FixedTrait::new(1, false))); + let cost = auction + .get_vrgda_price(to_days_fp(time + FixedTrait::new(1, false)), FixedTrait::ZERO()); + assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_0005, false)); +} + +#[test] +#[available_gas(20000000)] +fn test_pricing_basic() { + let auction = LinearVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + per_time_unit: FixedTrait::new_unscaled(2, false), + }; + let time_delta = FixedTrait::new(10368001, false); // 120 days + let num_mint = FixedTrait::new(239, true); + let cost = auction.get_vrgda_price(time_delta, num_mint); + assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); +} + +#[test] +#[available_gas(20000000)] +fn test_pricing_basic_reverse() { + let auction = LinearVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + per_time_unit: FixedTrait::new_unscaled(2, false), + }; + let time_delta = FixedTrait::new(10368001, false); // 120 days + let num_mint = FixedTrait::new(239, true); + let cost = auction.get_reverse_vrgda_price(time_delta, num_mint); + assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); +} + diff --git a/crates/dojo-defi/src/tests/logistic_vrgda_test.cairo b/crates/dojo-defi/src/tests/logistic_vrgda_test.cairo new file mode 100644 index 0000000000..c8e7a13cb2 --- /dev/null +++ b/crates/dojo-defi/src/tests/logistic_vrgda_test.cairo @@ -0,0 +1,62 @@ +use cubit::f128::types::fixed::{Fixed, FixedTrait}; + +use dojo_defi::dutch_auction::common::{from_days_fp}; +use dojo_defi::dutch_auction::vrgda::{LogisticVRGDA, LogisticVRGDATrait}; +use dojo_defi::tests::utils::assert_rel_approx_eq; + + +use debug::PrintTrait; +const _69_42: u128 = 1280572973596917000000; +const _0_31: u128 = 5718490662849961000; +const DELTA_0_0005: u128 = 9223372036854776; +const DELTA_0_02: u128 = 368934881474191000; +const MAX_SELLABLE: u128 = 6392; +const _0_0023: u128 = 42427511369531970; + +#[test] +#[available_gas(200000000)] +fn test_target_price() { + let auction = LogisticVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), + time_scale: FixedTrait::new(_0_0023, false), + }; + let time = from_days_fp(auction.get_target_sale_time(FixedTrait::new(1, false))); + + let cost = auction.get_vrgda_price(time + FixedTrait::new(1, false), FixedTrait::ZERO()); + assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_0005, false)); +} + +#[test] +#[available_gas(200000000)] +fn test_pricing_basic() { + let auction = LogisticVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), + time_scale: FixedTrait::new(_0_0023, false), + }; + let time_delta = FixedTrait::new(10368001, false); + let num_mint = FixedTrait::new(876, false); + + let cost = auction.get_vrgda_price(time_delta, num_mint); + assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); +} + +#[test] +#[available_gas(200000000)] +fn test_pricing_basic_reverse() { + let auction = LogisticVRGDA { + target_price: FixedTrait::new(_69_42, false), + decay_constant: FixedTrait::new(_0_31, false), + max_sellable: FixedTrait::new_unscaled(MAX_SELLABLE, false), + time_scale: FixedTrait::new(_0_0023, false), + }; + let time_delta = FixedTrait::new(10368001, false); + let num_mint = FixedTrait::new(876, false); + + let cost = auction.get_reverse_vrgda_price(time_delta, num_mint); + assert_rel_approx_eq(cost, auction.target_price, FixedTrait::new(DELTA_0_02, false)); +} + diff --git a/crates/dojo-defi/src/tests/utils.cairo b/crates/dojo-defi/src/tests/utils.cairo new file mode 100644 index 0000000000..470af7258c --- /dev/null +++ b/crates/dojo-defi/src/tests/utils.cairo @@ -0,0 +1,25 @@ +use cubit::f128::types::fixed::{Fixed, FixedTrait}; + +use debug::PrintTrait; + +const TOLERANCE: u128 = 18446744073709550; // 0.001 + +fn assert_approx_equal(expected: Fixed, actual: Fixed, tolerance: u128) { + let left_bound = expected - FixedTrait::new(tolerance, false); + let right_bound = expected + FixedTrait::new(tolerance, false); + assert(left_bound <= actual && actual <= right_bound, 'Not approx eq'); +} + +fn assert_rel_approx_eq(a: Fixed, b: Fixed, max_percent_delta: Fixed) { + if b == FixedTrait::ZERO() { + assert(a == b, 'a should eq ZERO'); + } + let percent_delta = if a > b { + (a - b) / b + } else { + (b - a) / b + }; + + assert(percent_delta < max_percent_delta, 'a ~= b not satisfied'); +} + diff --git a/crates/dojo-erc/Scarb.toml b/crates/dojo-erc/Scarb.toml index 74b8f6a42c..384b43b5af 100644 --- a/crates/dojo-erc/Scarb.toml +++ b/crates/dojo-erc/Scarb.toml @@ -1,5 +1,5 @@ [package] -cairo-version = "2.1.1" +cairo-version = "2.2.0" description = "Implementations of ERC standards for the Dojo framework" name = "dojo_erc" version = "0.2.1" @@ -24,6 +24,6 @@ test = "sozo test" [tool.dojo.env] # Katana rpc_url = "http://localhost:5050" -account_address = "0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0" -private_key = "0x0300001800000000300000180000000000030000000000003006001800006600" +account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" +private_key = "0x1800000000300000180000000000030000000000003006001800006600" world_address = "0x4cb5561a3a2a19d14f67f71a9da59c9194e2bbb44e1774260a111094fbd8f39" \ No newline at end of file diff --git a/crates/dojo-erc/src/erc1155.cairo b/crates/dojo-erc/src/erc1155.cairo deleted file mode 100644 index 7e4072ff84..0000000000 --- a/crates/dojo-erc/src/erc1155.cairo +++ /dev/null @@ -1,4 +0,0 @@ -mod components; -mod erc1155; -mod systems; -mod interface; diff --git a/crates/dojo-erc/src/erc1155/components.cairo b/crates/dojo-erc/src/erc1155/components.cairo deleted file mode 100644 index a86419295f..0000000000 --- a/crates/dojo-erc/src/erc1155/components.cairo +++ /dev/null @@ -1,88 +0,0 @@ -use starknet::ContractAddress; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; -use zeroable::Zeroable; -use array::{ArrayTrait, SpanTrait}; -use option::OptionTrait; - -// re-export components from erc_common -use dojo_erc::erc_common::components::{operator_approval, OperatorApproval, OperatorApprovalTrait}; - - -// -// Uri TODO: use BaseURI from erc_common -// - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Uri { - #[key] - token: ContractAddress, - uri: felt252 -} - -// -// ERC1155Balance -// - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct ERC1155Balance { - #[key] - token: ContractAddress, - #[key] - token_id: felt252, - #[key] - account: ContractAddress, - amount: u128 -} - -trait ERC1155BalanceTrait { - fn balance_of( - world: IWorldDispatcher, token: ContractAddress, account: ContractAddress, id: felt252 - ) -> u128; - fn transfer_tokens( - world: IWorldDispatcher, - token: ContractAddress, - from: ContractAddress, - to: ContractAddress, - ids: Span, - amounts: Span, - ); -} - -impl ERC1155BalanceImpl of ERC1155BalanceTrait { - fn balance_of( - world: IWorldDispatcher, token: ContractAddress, account: ContractAddress, id: felt252 - ) -> u128 { - // ERC1155: address zero is not a valid owner - assert(account.is_non_zero(), 'ERC1155: invalid owner address'); - get!(world, (token, id, account), ERC1155Balance).amount - } - - fn transfer_tokens( - world: IWorldDispatcher, - token: ContractAddress, - from: ContractAddress, - to: ContractAddress, - mut ids: Span, - mut amounts: Span, - ) { - loop { - if ids.len() == 0 { - break (); - } - let id = *ids.pop_front().unwrap(); - let amount: u128 = *amounts.pop_front().unwrap(); - - if (from.is_non_zero()) { - let mut from_balance = get!(world, (token, id, from), ERC1155Balance); - from_balance.amount -= amount; - set!(world, (from_balance)); - } - - if (to.is_non_zero()) { - let mut to_balance = get!(world, (token, id, to), ERC1155Balance); - to_balance.amount += amount; - set!(world, (to_balance)); - }; - }; - } -} diff --git a/crates/dojo-erc/src/erc1155/erc1155.cairo b/crates/dojo-erc/src/erc1155/erc1155.cairo deleted file mode 100644 index bcd71eee48..0000000000 --- a/crates/dojo-erc/src/erc1155/erc1155.cairo +++ /dev/null @@ -1,304 +0,0 @@ -#[starknet::contract] -mod ERC1155 { - use array::ArrayTrait; - use option::OptionTrait; - use clone::Clone; - use array::ArrayTCloneImpl; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; - use traits::{Into, TryInto}; - use zeroable::Zeroable; - use serde::Serde; - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - use dojo_erc::erc1155::components::{ - Uri, ERC1155BalanceTrait, OperatorApproval, OperatorApprovalTrait - }; - use dojo_erc::erc1155::interface::{ - IERC1155, IERC1155Metadata, IERC1155TokenReceiver, IERC1155TokenReceiverDispatcher, - IERC1155TokenReceiverDispatcherTrait, IERC1155_ID, IERC1155_METADATA_ID, - IERC1155_RECEIVER_ID - }; - use dojo_erc::erc165::interface::{IERC165, IERC165_ID}; - - use dojo_erc::erc_common::utils::{to_calldata, ToCallDataTrait, system_calldata}; - - use dojo_erc::erc1155::systems::{ - ERC1155SetApprovalForAllParams, ERC1155SafeTransferFromParams, - ERC1155SafeBatchTransferFromParams, ERC1155MintParams, ERC1155BurnParams - }; - - const UNLIMITED_ALLOWANCE: felt252 = - 3618502788666131213697322783095070105623107215331596699973092056135872020480; - - - #[derive(Clone, Drop, Serde, starknet::Event)] - struct TransferSingle { - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - id: u256, - value: u256 - } - - #[derive(Clone, Drop, Serde, starknet::Event)] - struct TransferBatch { - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - ids: Array, - values: Array - } - - #[derive(Clone, Drop, Serde, starknet::Event)] - struct ApprovalForAll { - owner: ContractAddress, - operator: ContractAddress, - approved: bool - } - - #[starknet::interface] - trait IERC1155Events { - fn on_transfer_single(ref self: ContractState, event: TransferSingle); - fn on_transfer_batch(ref self: ContractState, event: TransferBatch); - fn on_approval_for_all(ref self: ContractState, event: ApprovalForAll); - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - TransferSingle: TransferSingle, - TransferBatch: TransferBatch, - ApprovalForAll: ApprovalForAll - } - - #[storage] - struct Storage { - world: IWorldDispatcher, - owner_: ContractAddress - } - - // - // Constructor - // - - #[constructor] - fn constructor( - ref self: ContractState, world: ContractAddress, deployer: ContractAddress, uri: felt252 - ) { - self.world.write(IWorldDispatcher { contract_address: world }); - self.owner_.write(deployer); - self - .world - .read() - .execute('ERC1155SetUri', to_calldata(get_contract_address()).plus(uri).data); - } - - #[external(v0)] - impl ERC165 of IERC165 { - fn supports_interface(self: @ContractState, interface_id: u32) -> bool { - interface_id == IERC165_ID - || interface_id == IERC1155_ID - || interface_id == IERC1155_METADATA_ID - } - } - - #[external(v0)] - impl ERC1155Metadata of IERC1155Metadata { - fn uri(self: @ContractState, token_id: u256) -> felt252 { - let token = get_contract_address(); - let token_id_felt: felt252 = token_id.try_into().unwrap(); - get!(self.world.read(), (token), Uri).uri - } - } - - - #[external(v0)] - impl ERC1155 of IERC1155 { - fn balance_of(self: @ContractState, account: ContractAddress, id: u256) -> u256 { - ERC1155BalanceTrait::balance_of( - self.world.read(), get_contract_address(), account, id.try_into().unwrap() - ) - .into() - } - - fn balance_of_batch( - self: @ContractState, accounts: Array, ids: Array - ) -> Array { - assert(ids.len() == accounts.len(), 'ERC1155: invalid length'); - - let mut batch_balances = ArrayTrait::new(); - let mut index = 0; - loop { - if index == ids.len() { - break batch_balances.clone(); - } - batch_balances - .append( - ERC1155BalanceTrait::balance_of( - self.world.read(), - get_contract_address(), - *accounts.at(index), - (*ids.at(index)).try_into().unwrap() - ) - .into() - ); - index += 1; - } - } - - fn is_approved_for_all( - self: @ContractState, account: ContractAddress, operator: ContractAddress - ) -> bool { - OperatorApprovalTrait::is_approved_for_all( - self.world.read(), get_contract_address(), account, operator - ) - } - - fn set_approval_for_all( - ref self: ContractState, operator: ContractAddress, approved: bool - ) { - self - .world - .read() - .execute( - 'ERC1155SetApprovalForAll', - system_calldata( - ERC1155SetApprovalForAllParams { - token: get_contract_address(), - owner: get_caller_address(), - operator, - approved - } - ) - ); - } - - fn safe_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ) { - self - .world - .read() - .execute( - 'ERC1155SafeTransferFrom', - system_calldata( - ERC1155SafeTransferFromParams { - token: get_contract_address(), - operator: get_caller_address(), - from, - to, - id: id.try_into().unwrap(), - amount: amount.try_into().unwrap(), - data: data - } - ) - ); - } - - fn safe_batch_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - mut ids: Array, - mut amounts: Array, - data: Array - ) { - let mut idsf: Array = ArrayTrait::new(); - let mut amounts128: Array = ArrayTrait::new(); - loop { - if ids.len() == 0 { - break; - } - idsf.append(ids.pop_front().unwrap().try_into().unwrap()); - amounts128.append(amounts.pop_front().unwrap().try_into().unwrap()); - }; - - self - .world - .read() - .execute( - 'ERC1155SafeBatchTransferFrom', - system_calldata( - ERC1155SafeBatchTransferFromParams { - token: get_contract_address(), - operator: get_caller_address(), - from, - to, - ids: idsf, - amounts: amounts128, - data: data - } - ) - ); - } - } - - #[external(v0)] - impl ERC1155Events of IERC1155Events { - fn on_transfer_single(ref self: ContractState, event: TransferSingle) { - assert(get_caller_address() == self.world.read().executor(), 'ERC1155: not authorized'); - self.emit(event); - } - fn on_transfer_batch(ref self: ContractState, event: TransferBatch) { - assert(get_caller_address() == self.world.read().executor(), 'ERC1155: not authorized'); - self.emit(event); - } - fn on_approval_for_all(ref self: ContractState, event: ApprovalForAll) { - assert(get_caller_address() == self.world.read().executor(), 'ERC1155: not authorized'); - self.emit(event); - } - } - - - #[external(v0)] - #[generate_trait] - impl ERC721Custom of ERC721CustomTrait { - // TODO: use systems directly for these instead. - fn owner(self: @ContractState) -> ContractAddress { - self.owner_.read() - } - - fn mint( - ref self: ContractState, to: ContractAddress, id: felt252, amount: u128, data: Array - ) { - self - .world - .read() - .execute( - 'ERC1155Mint', - system_calldata( - ERC1155MintParams { - token: get_contract_address(), - operator: get_caller_address(), - to, - ids: array![id], - amounts: array![amount], - data: data - } - ) - ); - } - - fn burn(ref self: ContractState, from: ContractAddress, id: felt252, amount: u128) { - self - .world - .read() - .execute( - 'ERC1155Burn', - system_calldata( - ERC1155BurnParams { - token: get_contract_address(), - operator: get_caller_address(), - from, - ids: array![id], - amounts: array![amount] - } - ) - ); - } - } -} diff --git a/crates/dojo-erc/src/erc1155/interface.cairo b/crates/dojo-erc/src/erc1155/interface.cairo deleted file mode 100644 index 1604c812f8..0000000000 --- a/crates/dojo-erc/src/erc1155/interface.cairo +++ /dev/null @@ -1,105 +0,0 @@ -use starknet::ContractAddress; -use dojo_erc::erc165::interface::{IERC165}; - -// ERC165 -const IERC1155_ID: u32 = 0xd9b67a26_u32; -const IERC1155_METADATA_ID: u32 = 0x0e89341c_u32; -const IERC1155_RECEIVER_ID: u32 = 0x4e2312e0_u32; -const ON_ERC1155_RECEIVED_SELECTOR: u32 = 0xf23a6e61_u32; -const ON_ERC1155_BATCH_RECEIVED_SELECTOR: u32 = 0xbc197c81_u32; - - -// full interface IERC165 + IERC1155 + IERC1155Metadata + custom -#[starknet::interface] -trait IERC1155A { - // IERC165 - fn supports_interface(self: @TState, interface_id: u32) -> bool; - - // IERC1155 - fn safe_batch_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - ); - fn safe_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ); - fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); - fn is_approved_for_all( - self: @TState, account: ContractAddress, operator: ContractAddress - ) -> bool; - fn balance_of(self: @TState, account: ContractAddress, id: u256) -> u256; - fn balance_of_batch( - self: @TState, accounts: Array, ids: Array - ) -> Array; - - // IERC1155Metadata - fn uri(self: @TState, token_id: u256) -> felt252; - - // custom - fn owner(self: @TState) -> ContractAddress; - fn mint(ref self: TState, to: ContractAddress, id: felt252, amount: u128, data: Array); - fn burn(ref self: TState, from: ContractAddress, id: felt252, amount: u128); -} - -#[starknet::interface] -trait IERC1155 { - fn safe_batch_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - ); - fn safe_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array - ); - fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); - fn is_approved_for_all( - self: @TState, account: ContractAddress, operator: ContractAddress - ) -> bool; - fn balance_of(self: @TState, account: ContractAddress, id: u256) -> u256; - fn balance_of_batch( - self: @TState, accounts: Array, ids: Array - ) -> Array; -} - -#[starknet::interface] -trait IERC1155Metadata { - fn uri(self: @TState, token_id: u256) -> felt252; -} - -#[starknet::interface] -trait IERC1155TokenReceiver { - fn on_erc1155_received( - self: TState, - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - amount: u256, - data: Array - ) -> u32; - fn on_erc1155_batch_received( - self: TState, - operator: ContractAddress, - from: ContractAddress, - token_ids: Array, - amounts: Array, - data: Array - ) -> u32; -} - diff --git a/crates/dojo-erc/src/erc1155/systems.cairo b/crates/dojo-erc/src/erc1155/systems.cairo deleted file mode 100644 index ec95abde02..0000000000 --- a/crates/dojo-erc/src/erc1155/systems.cairo +++ /dev/null @@ -1,315 +0,0 @@ -use core::array::SpanTrait; -use starknet::{ContractAddress, get_contract_address}; -use zeroable::Zeroable; -use array::ArrayTrait; -use option::OptionTrait; -use serde::Serde; -use clone::Clone; -use traits::{Into, TryInto}; - -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::erc1155::erc1155::ERC1155::{ - ApprovalForAll, TransferSingle, TransferBatch, IERC1155EventsDispatcher, - IERC1155EventsDispatcherTrait -}; -use dojo_erc::erc1155::components::{ERC1155BalanceTrait, OperatorApprovalTrait}; -use dojo_erc::erc165::interface::{IERC165Dispatcher, IERC165DispatcherTrait, IACCOUNT_ID}; -use dojo_erc::erc1155::interface::{ - IERC1155TokenReceiverDispatcher, IERC1155TokenReceiverDispatcherTrait, IERC1155TokenReceiver, - IERC1155_RECEIVER_ID, ON_ERC1155_RECEIVED_SELECTOR, ON_ERC1155_BATCH_RECEIVED_SELECTOR -}; - -fn emit_transfer_single( - world: IWorldDispatcher, - token: ContractAddress, - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - id: felt252, - amount: u128 -) { - let event = TransferSingle { operator, from, to, id: id.into(), value: amount.into() }; - IERC1155EventsDispatcher { contract_address: token }.on_transfer_single(event.clone()); - emit!(world, event); -} - -fn emit_transfer_batch( - world: IWorldDispatcher, - token: ContractAddress, - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - mut ids: Span, - mut amounts: Span -) { - let mut ids_u256: Array = ArrayTrait::new(); - let mut amounts_u256: Array = ArrayTrait::new(); - loop { - if ids.len() == 0 { - break; - } - ids_u256.append((*ids.pop_front().unwrap()).into()); - amounts_u256.append((*amounts.pop_front().unwrap()).into()); - }; - let event = TransferBatch { - operator: operator, from: from, to: to, ids: ids_u256, values: amounts_u256, - }; - IERC1155EventsDispatcher { contract_address: token }.on_transfer_batch(event.clone()); - emit!(world, event); -} - -fn update( - world: IWorldDispatcher, - operator: ContractAddress, - token: ContractAddress, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array -) { - assert(ids.len() == amounts.len(), 'ERC1155: invalid length'); - - assert( - operator == from - || OperatorApprovalTrait::is_approved_for_all(world, token, from, operator), - 'ERC1155: insufficient approval' - ); - - ERC1155BalanceTrait::transfer_tokens(world, token, from, to, ids.span(), amounts.span()); - - if (ids.len() == 1) { - let id = *ids.at(0); - let amount = *amounts.at(0); - - emit_transfer_single(world, token, operator, from, to, id, amount); - // TODO: call do_safe_transfer_acceptance_check - // (not done as it would break tests). - } else { - emit_transfer_batch(world, token, operator, from, to, ids.span(), amounts.span()); - // TODO: call do_safe_batch_transfer_acceptance_check - // (not done as it would break tests). - } -} - -fn do_safe_transfer_acceptance_check( - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - id: u256, - amount: u256, - data: Array -) { - if (IERC165Dispatcher { contract_address: to }.supports_interface(IERC1155_RECEIVER_ID)) { - assert( - IERC1155TokenReceiverDispatcher { - contract_address: to - }.on_erc1155_received(operator, from, id, amount, data) == ON_ERC1155_RECEIVED_SELECTOR, - 'ERC1155: ERC1155Receiver reject' - ); - return (); - } - assert( - IERC165Dispatcher { contract_address: to }.supports_interface(IACCOUNT_ID), - 'Transfer to non-ERC1155Receiver' - ); -} - -fn do_safe_batch_transfer_acceptance_check( - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array -) { - if (IERC165Dispatcher { contract_address: to }.supports_interface(IERC1155_RECEIVER_ID)) { - assert( - IERC1155TokenReceiverDispatcher { - contract_address: to - } - .on_erc1155_batch_received( - operator, from, ids, amounts, data - ) == ON_ERC1155_BATCH_RECEIVED_SELECTOR, - 'ERC1155: ERC1155Receiver reject' - ); - return (); - } - assert( - IERC165Dispatcher { contract_address: to }.supports_interface(IACCOUNT_ID), - 'Transfer to non-ERC1155Receiver' - ); -} - -use ERC1155SetApprovalForAll::ERC1155SetApprovalForAllParams; -use ERC1155SafeTransferFrom::ERC1155SafeTransferFromParams; -use ERC1155SafeBatchTransferFrom::ERC1155SafeBatchTransferFromParams; -use ERC1155Mint::ERC1155MintParams; -use ERC1155Burn::ERC1155BurnParams; - -#[system] -mod ERC1155SetApprovalForAll { - use traits::Into; - use dojo::world::Context; - use starknet::ContractAddress; - use array::ArrayTrait; - use clone::Clone; - - use dojo_erc::erc1155::components::OperatorApprovalTrait; - use super::{IERC1155EventsDispatcher, IERC1155EventsDispatcherTrait, ApprovalForAll}; - - - #[derive(Drop, Serde)] - struct ERC1155SetApprovalForAllParams { - token: ContractAddress, - owner: ContractAddress, - operator: ContractAddress, - approved: bool, - } - - fn execute(ctx: Context, params: ERC1155SetApprovalForAllParams) { - let ERC1155SetApprovalForAllParams{token, owner, operator, approved } = params; - assert(owner != operator, 'ERC1155: wrong approval'); - - OperatorApprovalTrait::set_approval_for_all(ctx.world, token, owner, operator, approved); - - let event = ApprovalForAll { owner, operator, approved }; - IERC1155EventsDispatcher { contract_address: token }.on_approval_for_all(event.clone()); - emit!(ctx.world, event); - } -} - -// TODO uri storage may not fit in a single felt -#[system] -mod ERC1155SetUri { - use traits::Into; - use dojo::world::Context; - use dojo_erc::erc1155::components::Uri; - use starknet::ContractAddress; - - fn execute(ctx: Context, token: ContractAddress, uri: felt252) { - assert(ctx.origin == token, 'ERC1155: not authorized'); - let mut _uri = get!(ctx.world, (token), Uri); - _uri.uri = uri; - set!(ctx.world, (_uri)) - } -} - -#[system] -mod ERC1155SafeTransferFrom { - use traits::{Into, TryInto}; - use option::OptionTrait; - use array::ArrayTrait; - use dojo::world::Context; - use zeroable::Zeroable; - use starknet::ContractAddress; - - #[derive(Drop, Serde)] - struct ERC1155SafeTransferFromParams { - token: ContractAddress, - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - id: felt252, - amount: u128, - data: Array - } - - fn execute(ctx: Context, params: ERC1155SafeTransferFromParams) { - let ERC1155SafeTransferFromParams{token, operator, from, to, id, amount, data } = params; - assert(ctx.origin == operator || ctx.origin == token, 'ERC1155: not authorized'); - assert(to.is_non_zero(), 'ERC1155: to cannot be 0'); - - super::update(ctx.world, operator, token, from, to, array![id], array![amount], data); - } -} - -#[system] -mod ERC1155SafeBatchTransferFrom { - use traits::{Into, TryInto}; - use option::OptionTrait; - use array::ArrayTrait; - use dojo::world::Context; - use zeroable::Zeroable; - use starknet::ContractAddress; - - #[derive(Drop, Serde)] - struct ERC1155SafeBatchTransferFromParams { - token: ContractAddress, - operator: ContractAddress, - from: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - } - - fn execute(ctx: Context, params: ERC1155SafeBatchTransferFromParams) { - let ERC1155SafeBatchTransferFromParams{token, operator, from, to, ids, amounts, data } = - params; - - assert(ctx.origin == operator || ctx.origin == token, 'ERC1155: not authorized'); - assert(to.is_non_zero(), 'ERC1155: to cannot be 0'); - - super::update(ctx.world, operator, token, from, to, ids, amounts, data); - } -} - - -#[system] -mod ERC1155Mint { - use traits::{Into, TryInto}; - use option::OptionTrait; - use array::ArrayTrait; - use dojo::world::Context; - use zeroable::Zeroable; - use starknet::ContractAddress; - - #[derive(Drop, Serde)] - struct ERC1155MintParams { - token: ContractAddress, - operator: ContractAddress, - to: ContractAddress, - ids: Array, - amounts: Array, - data: Array - } - - fn execute(ctx: Context, params: ERC1155MintParams) { - let ERC1155MintParams{token, operator, to, ids, amounts, data } = params; - - assert(ctx.origin == operator || ctx.origin == token, 'ERC1155: not authorized'); - assert(to.is_non_zero(), 'ERC1155: invalid receiver'); - - super::update(ctx.world, operator, token, Zeroable::zero(), to, ids, amounts, data); - } -} - - -#[system] -mod ERC1155Burn { - use traits::{Into, TryInto}; - use option::OptionTrait; - use array::ArrayTrait; - use dojo::world::Context; - use zeroable::Zeroable; - use starknet::ContractAddress; - - #[derive(Drop, Serde)] - struct ERC1155BurnParams { - token: ContractAddress, - operator: ContractAddress, - from: ContractAddress, - ids: Array, - amounts: Array - } - - fn execute(ctx: Context, params: ERC1155BurnParams) { - let ERC1155BurnParams{token, operator, from, ids, amounts } = params; - assert(ctx.origin == operator || ctx.origin == token, 'ERC1155: not authorized'); - assert(from.is_non_zero(), 'ERC1155: invalid sender'); - - super::update(ctx.world, operator, token, from, Zeroable::zero(), ids, amounts, array![]); - } -} diff --git a/crates/dojo-erc/src/erc165.cairo b/crates/dojo-erc/src/erc165.cairo deleted file mode 100644 index c7f08b4631..0000000000 --- a/crates/dojo-erc/src/erc165.cairo +++ /dev/null @@ -1,3 +0,0 @@ -mod interface; - -use interface::{IERC165, IERC165Dispatcher, IERC165DispatcherTrait}; diff --git a/crates/dojo-erc/src/erc165/interface.cairo b/crates/dojo-erc/src/erc165/interface.cairo deleted file mode 100644 index b8a067796b..0000000000 --- a/crates/dojo-erc/src/erc165/interface.cairo +++ /dev/null @@ -1,8 +0,0 @@ -// ERC165 -const IERC165_ID: u32 = 0x01ffc9a7_u32; -const IACCOUNT_ID: u32 = 0xa66bd575_u32; - -#[starknet::interface] -trait IERC165 { - fn supports_interface(self: @TState, interface_id: u32) -> bool; -} diff --git a/crates/dojo-erc/src/erc20.cairo b/crates/dojo-erc/src/erc20.cairo deleted file mode 100644 index 262673d978..0000000000 --- a/crates/dojo-erc/src/erc20.cairo +++ /dev/null @@ -1,3 +0,0 @@ -mod erc20; -mod components; -mod systems; diff --git a/crates/dojo-erc/src/erc20/components.cairo b/crates/dojo-erc/src/erc20/components.cairo deleted file mode 100644 index 0a0b52ce51..0000000000 --- a/crates/dojo-erc/src/erc20/components.cairo +++ /dev/null @@ -1,28 +0,0 @@ -use starknet::ContractAddress; - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Allowance { - #[key] - token: ContractAddress, - #[key] - owner: ContractAddress, - #[key] - spender: ContractAddress, - amount: felt252, -} - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Balance { - #[key] - token: ContractAddress, - #[key] - sender: ContractAddress, - amount: felt252, -} - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Supply { - #[key] - token: ContractAddress, - amount: felt252 -} diff --git a/crates/dojo-erc/src/erc20/erc20.cairo b/crates/dojo-erc/src/erc20/erc20.cairo deleted file mode 100644 index fdbfb14e91..0000000000 --- a/crates/dojo-erc/src/erc20/erc20.cairo +++ /dev/null @@ -1,188 +0,0 @@ -// TODO: future improvements when Cairo catches up -// * use BoundedInt in allowance calc -// * use inline commands (currently available only in systems) -// * use ufelt when available - -#[starknet::contract] -mod ERC20 { - // max(felt252) - const UNLIMITED_ALLOWANCE: felt252 = - 3618502788666131213697322783095070105623107215331596699973092056135872020480; - - use array::ArrayTrait; - use option::OptionTrait; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; - use traits::Into; - use zeroable::Zeroable; - - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - use dojo_erc::erc20::components::{Allowance, Balance, Supply}; - - #[storage] - struct Storage { - world: IWorldDispatcher, - token_name: felt252, - token_symbol: felt252, - token_decimals: u8, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - Transfer: Transfer, - Approval: Approval - } - - - #[derive(Drop, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - value: u256 - } - - #[derive(Drop, starknet::Event)] - struct Approval { - owner: ContractAddress, - spender: ContractAddress, - value: u256 - } - - #[constructor] - fn constructor( - ref self: ContractState, - world: ContractAddress, - name: felt252, - symbol: felt252, - decimals: u8, - initial_supply: felt252, - recipient: ContractAddress - ) { - self.world.write(IWorldDispatcher { contract_address: world }); - self.token_name.write(name); - self.token_symbol.write(symbol); - self.token_decimals.write(decimals); - - if initial_supply != 0 { - assert(recipient.is_non_zero(), 'ERC20: mint to 0'); - let token = get_contract_address(); - let mut calldata = ArrayTrait::new(); - calldata.append(token.into()); - calldata.append(recipient.into()); - calldata.append(initial_supply); - self.world.read().execute('erc20_mint', calldata); - - self - .emit( - Transfer { from: Zeroable::zero(), to: recipient, value: initial_supply.into() } - ); - } - } - - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - self.token_name.read() - } - - #[external(v0)] - fn symbol(self: @ContractState) -> felt252 { - self.token_symbol.read() - } - - #[external(v0)] - fn decimals(self: @ContractState) -> u8 { - self.token_decimals.read() - } - - #[external(v0)] - fn total_supply(self: @ContractState) -> u256 { - let contract_address = get_contract_address(); - let supply = get!(self.world.read(), contract_address, Supply); - supply.amount.into() - } - - #[external(v0)] - fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - let token = get_contract_address(); - let balance = get!(self.world.read(), (token, account), Balance); - balance.amount.into() - } - - #[external(v0)] - fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 { - let token = get_contract_address(); - let allowance = get!(self.world.read(), (token, owner, spender), Allowance); - allowance.amount.into() - } - - #[external(v0)] - fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { - assert(spender.is_non_zero(), 'ERC20: approve to 0'); - - let token = get_contract_address(); - let owner = get_caller_address(); - let mut calldata = ArrayTrait::new(); - calldata.append(token.into()); - calldata.append(owner.into()); - calldata.append(spender.into()); - calldata.append(u256_as_allowance(amount)); - self.world.read().execute('erc20_approve', calldata); - - self.emit(Approval { owner, spender, value: amount }); - - true - } - - #[external(v0)] - fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { - transfer_internal(ref self, get_caller_address(), recipient, amount); - true - } - - #[external(v0)] - fn transfer_from( - ref self: ContractState, spender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool { - transfer_internal(ref self, spender, recipient, amount); - true - } - - // - // Internal - // - - fn transfer_internal( - ref self: ContractState, spender: ContractAddress, recipient: ContractAddress, amount: u256 - ) { - assert(recipient.is_non_zero(), 'ERC20: transfer to 0'); - - let token = get_contract_address(); - let mut calldata = ArrayTrait::new(); - calldata.append(token.into()); - calldata.append(get_caller_address().into()); - calldata.append(spender.into()); - calldata.append(recipient.into()); - calldata.append(u256_into_felt252(amount)); - - self.world.read().execute('erc20_transfer_from', calldata); - - self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); - } - - fn u256_as_allowance(val: u256) -> felt252 { - // by convention, max(u256) means unlimited amount, - // but since we're using felts, use max(felt252) to do the same - // TODO: use BoundedInt when available - let max_u128 = 0xffffffffffffffffffffffffffffffff; - let max_u256 = u256 { low: max_u128, high: max_u128 }; - if val == max_u256 { - return UNLIMITED_ALLOWANCE; - } - u256_into_felt252(val) - } - - fn u256_into_felt252(val: u256) -> felt252 { - // temporary, until TryInto of this is in corelib - val.low.into() + val.high.into() * 0x100000000000000000000000000000000 - } -} diff --git a/crates/dojo-erc/src/erc20/lib.cairo b/crates/dojo-erc/src/erc20/lib.cairo deleted file mode 100644 index 262673d978..0000000000 --- a/crates/dojo-erc/src/erc20/lib.cairo +++ /dev/null @@ -1,3 +0,0 @@ -mod erc20; -mod components; -mod systems; diff --git a/crates/dojo-erc/src/erc20/systems.cairo b/crates/dojo-erc/src/erc20/systems.cairo deleted file mode 100644 index 9f7c47955c..0000000000 --- a/crates/dojo-erc/src/erc20/systems.cairo +++ /dev/null @@ -1,113 +0,0 @@ -#[system] -mod erc20_approve { - use traits::Into; - use starknet::ContractAddress; - use dojo::world::Context; - use dojo_erc::erc20::components::Allowance; - - fn execute( - ctx: Context, - token: ContractAddress, - owner: ContractAddress, - spender: ContractAddress, - amount: felt252 - ) { - set!(ctx.world, Allowance { token, owner, spender, amount }) - } -} - -#[system] -mod erc20_transfer_from { - const UNLIMITED_ALLOWANCE: felt252 = - 3618502788666131213697322783095070105623107215331596699973092056135872020480; - - use starknet::ContractAddress; - use traits::Into; - use zeroable::Zeroable; - use dojo::world::Context; - use dojo_erc::erc20::components::{Allowance, Balance}; - - fn execute( - ctx: Context, - token: ContractAddress, - caller: ContractAddress, - spender: ContractAddress, - recipient: ContractAddress, - amount: felt252 - ) { - assert(token == ctx.origin, 'ERC20: not authorized'); - assert(spender.is_non_zero(), 'ERC20: transfer from 0'); - assert(recipient.is_non_zero(), 'ERC20: transfer to 0'); - - if spender != caller { - // decrease allowance if it's not owner doing the transfer - let mut allowance = get!(ctx.world, (token, caller, spender), Allowance); - if !is_unlimited_allowance(allowance) { - allowance.amount -= amount; - set!(ctx.world, (allowance)); - } - } - - // decrease spender's balance - let mut balance = get!(ctx.world, (token, spender), Balance); - balance.amount -= amount; - set!(ctx.world, (balance)); - - // increase recipient's balance - let mut balance = get!(ctx.world, (token, recipient), Balance); - balance.amount += amount; - set!(ctx.world, (balance)); - } - - fn is_unlimited_allowance(allowance: Allowance) -> bool { - allowance.amount == UNLIMITED_ALLOWANCE - } -} - -#[system] -mod erc20_mint { - use starknet::ContractAddress; - use traits::Into; - use zeroable::Zeroable; - use dojo::world::Context; - use dojo_erc::erc20::components::{Balance, Supply}; - - fn execute(ctx: Context, token: ContractAddress, recipient: ContractAddress, amount: felt252) { - assert(token == ctx.origin, 'ERC20: not authorized'); - assert(recipient.is_non_zero(), 'ERC20: mint to 0'); - - // increase token supply - let mut supply = get!(ctx.world, token, Supply); - supply.amount += amount; - set!(ctx.world, (supply)); - - // increase balance of recipient - let mut balance = get!(ctx.world, (token, recipient), Balance); - balance.amount -= amount; - set!(ctx.world, (balance)); - } -} - -#[system] -mod erc20_burn { - use starknet::ContractAddress; - use traits::Into; - use zeroable::Zeroable; - use dojo::world::Context; - use dojo_erc::erc20::components::{Balance, Supply}; - - fn execute(ctx: Context, token: ContractAddress, owner: ContractAddress, amount: felt252) { - assert(token == ctx.origin, 'ERC20: not authorized'); - assert(owner.is_non_zero(), 'ERC20: burn from 0'); - - // decrease token supply - let mut supply = get!(ctx.world, token, Supply); - supply.amount -= amount; - set!(ctx.world, (supply)); - - // decrease balance of owner - let mut balance = get!(ctx.world, (token, owner), Balance); - balance.amount -= amount; - set!(ctx.world, (balance)); - } -} diff --git a/crates/dojo-erc/src/erc721.cairo b/crates/dojo-erc/src/erc721.cairo deleted file mode 100644 index 0dd5fcd51f..0000000000 --- a/crates/dojo-erc/src/erc721.cairo +++ /dev/null @@ -1,4 +0,0 @@ -mod components; -mod erc721; -mod systems; -mod interface; diff --git a/crates/dojo-erc/src/erc721/components.cairo b/crates/dojo-erc/src/erc721/components.cairo deleted file mode 100644 index 85375b5a35..0000000000 --- a/crates/dojo-erc/src/erc721/components.cairo +++ /dev/null @@ -1,172 +0,0 @@ -use starknet::ContractAddress; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use zeroable::Zeroable; -use array::{ArrayTrait, SpanTrait}; -use option::OptionTrait; - -// re-export components from erc_common -use dojo_erc::erc_common::components::{ - operator_approval, OperatorApproval, OperatorApprovalTrait, base_uri, BaseUri, BaseUriTrait -}; - -// -// ERC721Owner -// - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct ERC721Owner { - #[key] - token: ContractAddress, - #[key] - token_id: felt252, - address: ContractAddress -} - - -trait ERC721OwnerTrait { - fn owner_of( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252 - ) -> ContractAddress; - fn set_owner( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252, account: ContractAddress - ); -} - -impl ERC721OwnerImpl of ERC721OwnerTrait { - // ERC721: address zero is not a valid owner - - fn owner_of( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252 - ) -> ContractAddress { - get!(world, (token, token_id), ERC721Owner).address - } - - // perform safety checks before calling this fn - fn set_owner( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252, account: ContractAddress - ) { - let mut owner = get!(world, (token, token_id), ERC721Owner); - owner.address = account; - set!(world, (owner)); - } -} - - -// -// ERC721Balance -// - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct ERC721Balance { - #[key] - token: ContractAddress, - #[key] - account: ContractAddress, - amount: u128, -} - -trait ERC721BalanceTrait { - fn balance_of( - world: IWorldDispatcher, token: ContractAddress, account: ContractAddress - ) -> u128; - fn transfer_token( - world: IWorldDispatcher, - token: ContractAddress, - from: ContractAddress, - to: ContractAddress, - amount: u128, - ); - - fn increase_balance( - world: IWorldDispatcher, token: ContractAddress, owner: ContractAddress, amount: u128, - ); - - fn decrease_balance( - world: IWorldDispatcher, token: ContractAddress, owner: ContractAddress, amount: u128, - ); -} - -impl ERC721BalanceImpl of ERC721BalanceTrait { - fn balance_of( - world: IWorldDispatcher, token: ContractAddress, account: ContractAddress - ) -> u128 { - // ERC721: address zero is not a valid owner - assert(account.is_non_zero(), 'ERC721: invalid owner address'); - get!(world, (token, account), ERC721Balance).amount - } - - fn transfer_token( - world: IWorldDispatcher, - token: ContractAddress, - from: ContractAddress, - to: ContractAddress, - amount: u128, - ) { - let mut from_balance = get!(world, (token, from), ERC721Balance); - from_balance.amount -= amount; - set!(world, (from_balance)); - - let mut to_balance = get!(world, (token, to), ERC721Balance); - to_balance.amount += amount; - set!(world, (to_balance)); - } - - fn increase_balance( - world: IWorldDispatcher, token: ContractAddress, owner: ContractAddress, amount: u128, - ) { - let mut balance = get!(world, (token, owner), ERC721Balance); - balance.amount += amount; - set!(world, (balance)); - } - - fn decrease_balance( - world: IWorldDispatcher, token: ContractAddress, owner: ContractAddress, amount: u128, - ) { - let mut balance = get!(world, (token, owner), ERC721Balance); - balance.amount -= amount; - set!(world, (balance)); - } -} - - -// -// ERC721TokenApproval -// - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct ERC721TokenApproval { - #[key] - token: ContractAddress, - #[key] - token_id: felt252, - address: ContractAddress, -} - - -trait ERC721TokenApprovalTrait { - fn get_approved( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252 - ) -> ContractAddress; - - fn approve( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252, to: ContractAddress - ); -} - -impl ERC721TokenApprovalImpl of ERC721TokenApprovalTrait { - fn get_approved( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252 - ) -> ContractAddress { - let approval = get!(world, (token, token_id), ERC721TokenApproval); - approval.address - } - - fn approve( - world: IWorldDispatcher, token: ContractAddress, token_id: felt252, to: ContractAddress - ) { - let mut approval = get!(world, (token, token_id), ERC721TokenApproval); - approval.address = to; - set!(world, (approval)) - } -} diff --git a/crates/dojo-erc/src/erc721/erc721.cairo b/crates/dojo-erc/src/erc721/erc721.cairo deleted file mode 100644 index 9614390daf..0000000000 --- a/crates/dojo-erc/src/erc721/erc721.cairo +++ /dev/null @@ -1,284 +0,0 @@ -#[starknet::contract] -mod ERC721 { - use array::ArrayTrait; - use option::OptionTrait; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; - use traits::{Into, TryInto}; - use zeroable::Zeroable; - use serde::Serde; - use clone::Clone; - - use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - use dojo_erc::erc721::components::{ - ERC721Owner, ERC721OwnerTrait, BaseUri, BaseUriTrait, ERC721Balance, ERC721BalanceTrait, - ERC721TokenApproval, ERC721TokenApprovalTrait, OperatorApproval, OperatorApprovalTrait - }; - use dojo_erc::erc721::systems::{ - ERC721ApproveParams, ERC721SetApprovalForAllParams, ERC721TransferFromParams, - ERC721MintParams, ERC721BurnParams - }; - - use dojo_erc::erc165::interface::{IERC165, IERC165_ID}; - use dojo_erc::erc721::interface::{IERC721, IERC721Metadata, IERC721_ID, IERC721_METADATA_ID}; - - use dojo_erc::erc_common::utils::{to_calldata, ToCallDataTrait, system_calldata}; - - - #[storage] - struct Storage { - world: IWorldDispatcher, - owner_: ContractAddress, // TODO: move in components - name_: felt252, - symbol_: felt252, - } - - #[derive(Clone, Drop, Serde, PartialEq, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - token_id: u256 - } - - #[derive(Clone, Drop, Serde, PartialEq, starknet::Event)] - struct Approval { - owner: ContractAddress, - to: ContractAddress, - token_id: u256 - } - - #[derive(Clone, Drop, Serde, PartialEq, starknet::Event)] - struct ApprovalForAll { - owner: ContractAddress, - operator: ContractAddress, - approved: bool - } - - #[event] - #[derive(Drop, PartialEq, starknet::Event)] - enum Event { - Transfer: Transfer, - Approval: Approval, - ApprovalForAll: ApprovalForAll - } - - #[starknet::interface] - trait IERC721Events { - fn on_transfer(ref self: ContractState, event: Transfer); - fn on_approval(ref self: ContractState, event: Approval); - fn on_approval_for_all(ref self: ContractState, event: ApprovalForAll); - } - - // - // Constructor - // - - #[constructor] - fn constructor( - ref self: ContractState, - world: IWorldDispatcher, - owner: ContractAddress, - name: felt252, - symbol: felt252, - uri: felt252, - ) { - self.world.write(world); - self.owner_.write(owner); - self.name_.write(name); - self.symbol_.write(symbol); - - world.execute('ERC721SetBaseUri', to_calldata(get_contract_address()).plus(uri).data); - } - - - #[external(v0)] - impl ERC165 of IERC165 { - fn supports_interface(self: @ContractState, interface_id: u32) -> bool { - interface_id == IERC165_ID - || interface_id == IERC721_ID - || interface_id == IERC721_METADATA_ID - } - } - - #[external(v0)] - impl ERC721 of IERC721 { - fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - ERC721BalanceTrait::balance_of(self.world.read(), get_contract_address(), account) - .into() - } - - fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { - let owner = ERC721OwnerTrait::owner_of( - self.world.read(), get_contract_address(), token_id.try_into().unwrap() - ); - assert(owner.is_non_zero(), 'ERC721: invalid token_id'); - owner - } - - fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { - assert(self.exists(token_id), 'ERC721: invalid token_id'); - - let token_id_felt: felt252 = token_id.try_into().unwrap(); - ERC721TokenApprovalTrait::get_approved( - self.world.read(), get_contract_address(), token_id_felt - ) - } - - fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { - self - .world - .read() - .execute( - 'ERC721Approve', - system_calldata( - ERC721ApproveParams { - token: get_contract_address(), - caller: get_caller_address(), - token_id: token_id.try_into().unwrap(), - to - } - ) - ); - } - - fn is_approved_for_all( - self: @ContractState, owner: ContractAddress, operator: ContractAddress - ) -> bool { - OperatorApprovalTrait::is_approved_for_all( - self.world.read(), get_contract_address(), owner, operator - ) - } - - fn set_approval_for_all( - ref self: ContractState, operator: ContractAddress, approved: bool - ) { - self - .world - .read() - .execute( - 'ERC721SetApprovalForAll', - system_calldata( - ERC721SetApprovalForAllParams { - token: get_contract_address(), - owner: get_caller_address(), - operator, - approved - } - ) - ); - } - - fn transfer_from( - ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 - ) { - self - .world - .read() - .execute( - 'ERC721TransferFrom', - system_calldata( - ERC721TransferFromParams { - token: get_contract_address(), - caller: get_caller_address(), - from, - to, - token_id: token_id.try_into().unwrap() - } - ) - ); - } - - fn safe_transfer_from( - ref self: ContractState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ) { - // TODO: check if we should do it - panic(array!['not implemented !']); - } - } - - #[external(v0)] - impl ERC721Metadata of IERC721Metadata { - fn name(self: @ContractState) -> felt252 { - self.name_.read() - } - - fn symbol(self: @ContractState) -> felt252 { - self.symbol_.read() - } - - fn token_uri(self: @ContractState, token_id: u256) -> felt252 { - // TODO : add token_id to base_uri - assert(self.exists(token_id), 'ERC721: invalid token_id'); - BaseUriTrait::get_base_uri(self.world.read(), get_contract_address()) - } - } - - - #[external(v0)] - #[generate_trait] - impl ERC721Custom of ERC721CustomTrait { - fn exists(self: @ContractState, token_id: u256) -> bool { - self.owner_of(token_id).is_non_zero() - } - - fn owner(self: @ContractState) -> ContractAddress { - self.owner_.read() - } - - fn transfer(ref self: ContractState, to: ContractAddress, token_id: u256) { - self.transfer_from(get_caller_address(), to, token_id); - } - - fn mint(ref self: ContractState, to: ContractAddress, token_id: u256) { - self - .world - .read() - .execute( - 'ERC721Mint', - system_calldata( - ERC721MintParams { - token: get_contract_address(), - recipient: to, - token_id: token_id.try_into().unwrap() - } - ) - ); - } - - fn burn(ref self: ContractState, token_id: u256) { - self - .world - .read() - .execute( - 'ERC721Burn', - system_calldata( - ERC721BurnParams { - token: get_contract_address(), - caller: get_caller_address(), - token_id: token_id.try_into().unwrap() - } - ) - ); - } - } - - - #[external(v0)] - impl ERC721EventEmitter of IERC721Events { - fn on_transfer(ref self: ContractState, event: Transfer) { - assert(get_caller_address() == self.world.read().executor(), 'ERC721: not authorized'); - self.emit(event); - } - fn on_approval(ref self: ContractState, event: Approval) { - assert(get_caller_address() == self.world.read().executor(), 'ERC721: not authorized'); - self.emit(event); - } - fn on_approval_for_all(ref self: ContractState, event: ApprovalForAll) { - assert(get_caller_address() == self.world.read().executor(), 'ERC721: not authorized'); - self.emit(event); - } - } -} diff --git a/crates/dojo-erc/src/erc721/interface.cairo b/crates/dojo-erc/src/erc721/interface.cairo deleted file mode 100644 index 43413dff35..0000000000 --- a/crates/dojo-erc/src/erc721/interface.cairo +++ /dev/null @@ -1,79 +0,0 @@ -use starknet::ContractAddress; - -// ERC165 -const IERC721_ID: u32 = 0x80ac58cd_u32; -const IERC721_METADATA_ID: u32 = 0x5b5e139f_u32; -const IERC721_RECEIVER_ID: u32 = 0x150b7a02_u32; -const IERC721_ENUMERABLE_ID: u32 = 0x780e9d63_u32; - - -// full interface IERC165 + IERC721 + IERC721Metadata + custom -#[starknet::interface] -trait IERC721A { - // IERC165 - fn supports_interface(self: @TState, interface_id: u32) -> bool; - - // IERC721 - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn owner_of(self: @TState, token_id: u256) -> ContractAddress; - - fn approve(ref self: TState, to: ContractAddress, token_id: u256); - fn get_approved(self: @TState, token_id: u256) -> ContractAddress; - fn is_approved_for_all( - self: @TState, owner: ContractAddress, operator: ContractAddress - ) -> bool; - fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); - - fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); - fn safe_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); - - // IERC721Metadata - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn token_uri(self: @TState, token_id: u256) -> felt252; - - // custom - fn owner(self: @TState) -> ContractAddress; - fn mint(ref self: TState, to: ContractAddress, token_id: u256); - fn burn(ref self: TState, token_id: u256); - fn exists(self: @TState, token_id: u256) -> bool; - fn transfer(ref self: TState, to: ContractAddress, token_id: u256); -} - - -#[starknet::interface] -trait IERC721 { - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn owner_of(self: @TState, token_id: u256) -> ContractAddress; - - fn approve(ref self: TState, to: ContractAddress, token_id: u256); - fn get_approved(self: @TState, token_id: u256) -> ContractAddress; - - fn is_approved_for_all( - self: @TState, owner: ContractAddress, operator: ContractAddress - ) -> bool; - fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); - - fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); - fn safe_transfer_from( - ref self: TState, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); -} - - -#[starknet::interface] -trait IERC721Metadata { - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn token_uri(self: @TState, token_id: u256) -> felt252; -} diff --git a/crates/dojo-erc/src/erc721/systems.cairo b/crates/dojo-erc/src/erc721/systems.cairo deleted file mode 100644 index a5d1c3a5a5..0000000000 --- a/crates/dojo-erc/src/erc721/systems.cairo +++ /dev/null @@ -1,276 +0,0 @@ -use array::{ArrayTrait, SpanTrait}; -use option::OptionTrait; -use serde::Serde; -use clone::Clone; -use traits::{Into, TryInto}; -use starknet::{ContractAddress, get_contract_address}; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; -use dojo_erc::erc721::erc721::ERC721; - -use dojo_erc::erc721::erc721::ERC721::{ - IERC721EventsDispatcher, IERC721EventsDispatcherTrait, Approval, Transfer, ApprovalForAll -}; - -use ERC721Approve::ERC721ApproveParams; -use ERC721SetApprovalForAll::ERC721SetApprovalForAllParams; -use ERC721TransferFrom::ERC721TransferFromParams; -use ERC721Mint::ERC721MintParams; -use ERC721Burn::ERC721BurnParams; - -fn emit_transfer( - world: IWorldDispatcher, - token: ContractAddress, - from: ContractAddress, - to: ContractAddress, - token_id: felt252, -) { - let event = Transfer { from, to, token_id: token_id.into() }; - IERC721EventsDispatcher { contract_address: token }.on_transfer(event.clone()); - emit!(world, event); -} - -fn emit_approval( - world: IWorldDispatcher, - token: ContractAddress, - owner: ContractAddress, - to: ContractAddress, - token_id: felt252, -) { - let event = Approval { owner, to, token_id: token_id.into() }; - IERC721EventsDispatcher { contract_address: token }.on_approval(event.clone()); - emit!(world, event); -} - - -fn emit_approval_for_all( - world: IWorldDispatcher, - token: ContractAddress, - owner: ContractAddress, - operator: ContractAddress, - approved: bool, -) { - let event = ApprovalForAll { owner, operator, approved }; - IERC721EventsDispatcher { contract_address: token }.on_approval_for_all(event.clone()); - emit!(world, event); -} - - -#[system] -mod ERC721Approve { - use starknet::ContractAddress; - use traits::{Into, TryInto}; - use option::{OptionTrait}; - use clone::Clone; - use array::{ArrayTrait, SpanTrait}; - - use dojo::world::Context; - use dojo_erc::erc721::components::{ - ERC721OwnerTrait, ERC721TokenApprovalTrait, OperatorApprovalTrait - }; - use super::{IERC721EventsDispatcher, IERC721EventsDispatcherTrait, Approval}; - use zeroable::Zeroable; - - #[derive(Drop, Serde)] - struct ERC721ApproveParams { - token: ContractAddress, - caller: ContractAddress, - token_id: felt252, - to: ContractAddress - } - - fn execute(ctx: Context, params: ERC721ApproveParams) { - let ERC721ApproveParams{token, caller, token_id, to } = params; - - assert(token == ctx.origin, 'ERC721: not authorized'); - assert(caller != to, 'ERC721: invalid self approval'); - - let owner = ERC721OwnerTrait::owner_of(ctx.world, token, token_id); - assert(owner.is_non_zero(), 'ERC721: invalid token_id'); - - let is_approved_for_all = OperatorApprovalTrait::is_approved_for_all( - ctx.world, token, owner, caller - ); - // // ERC721: approve caller is not token owner or approved for all - assert(caller == owner || is_approved_for_all, 'ERC721: unauthorized caller'); - ERC721TokenApprovalTrait::approve(ctx.world, token, token_id, to, ); - - // emit events - super::emit_approval(ctx.world, token, owner, to, token_id); - } -} - -#[system] -mod ERC721SetApprovalForAll { - use starknet::ContractAddress; - use traits::Into; - use dojo::world::Context; - use clone::Clone; - use array::{ArrayTrait, SpanTrait}; - - use dojo_erc::erc721::components::{OperatorApprovalTrait}; - - - #[derive(Drop, Serde)] - struct ERC721SetApprovalForAllParams { - token: ContractAddress, - owner: ContractAddress, - operator: ContractAddress, - approved: bool - } - - fn execute(ctx: Context, params: ERC721SetApprovalForAllParams) { - let ERC721SetApprovalForAllParams{token, owner, operator, approved } = params; - - assert(token == ctx.origin, 'ERC721: not authorized'); - assert(owner != operator, 'ERC721: self approval'); - - OperatorApprovalTrait::set_approval_for_all(ctx.world, token, owner, operator, approved); - - // emit event - super::emit_approval_for_all(ctx.world, token, owner, operator, approved); - } -} - -#[system] -mod ERC721TransferFrom { - use starknet::ContractAddress; - use traits::Into; - use zeroable::Zeroable; - use array::SpanTrait; - - use dojo::world::Context; - use dojo_erc::erc721::components::{ - OperatorApprovalTrait, ERC721BalanceTrait, ERC721TokenApprovalTrait, ERC721OwnerTrait, - }; - - #[derive(Drop, Serde)] - struct ERC721TransferFromParams { - caller: ContractAddress, - token: ContractAddress, - from: ContractAddress, - to: ContractAddress, - token_id: felt252 - } - - fn execute(ctx: Context, params: ERC721TransferFromParams) { - let ERC721TransferFromParams{caller, token, from, to, token_id } = params; - - assert(token == ctx.origin, 'ERC721: not authorized'); - assert(!to.is_zero(), 'ERC721: invalid receiver'); - - let owner = ERC721OwnerTrait::owner_of(ctx.world, token, token_id); - assert(owner.is_non_zero(), 'ERC721: invalid token_id'); - - let is_approved_for_all = OperatorApprovalTrait::is_approved_for_all( - ctx.world, token, owner, caller - ); - let approved = ERC721TokenApprovalTrait::get_approved(ctx.world, token, token_id); - - assert( - owner == caller || is_approved_for_all || approved == caller, - 'ERC721: unauthorized caller' - ); - - ERC721OwnerTrait::set_owner(ctx.world, token, token_id, to); - ERC721BalanceTrait::transfer_token(ctx.world, token, from, to, 1); - ERC721TokenApprovalTrait::approve(ctx.world, token, token_id, Zeroable::zero()); - - // emit events - super::emit_transfer(ctx.world, token, from, to, token_id); - } -} - -#[system] -mod ERC721Mint { - use starknet::ContractAddress; - use traits::Into; - use zeroable::Zeroable; - use array::SpanTrait; - - use dojo::world::Context; - use dojo_erc::erc721::components::{ERC721BalanceTrait, ERC721OwnerTrait}; - - #[derive(Drop, Serde)] - struct ERC721MintParams { - token: ContractAddress, - recipient: ContractAddress, - token_id: felt252 - } - - fn execute(ctx: Context, params: ERC721MintParams) { - let ERC721MintParams{token, recipient, token_id } = params; - - assert(token == ctx.origin, 'ERC721: not authorized'); - assert(recipient.is_non_zero(), 'ERC721: mint to 0'); - - let owner = ERC721OwnerTrait::owner_of(ctx.world, token, token_id); - assert(owner.is_zero(), 'ERC721: already minted'); - - ERC721BalanceTrait::increase_balance(ctx.world, token, recipient, 1); - ERC721OwnerTrait::set_owner(ctx.world, token, token_id, recipient); - // emit events - super::emit_transfer(ctx.world, token, Zeroable::zero(), recipient, token_id); - } -} - -#[system] -mod ERC721Burn { - use starknet::ContractAddress; - use traits::Into; - use zeroable::Zeroable; - use array::SpanTrait; - - use dojo::world::Context; - use dojo_erc::erc721::components::{ - ERC721BalanceTrait, ERC721OwnerTrait, ERC721TokenApprovalTrait, OperatorApprovalTrait, - }; - - #[derive(Drop, Serde)] - struct ERC721BurnParams { - token: ContractAddress, - caller: ContractAddress, - token_id: felt252 - } - - fn execute(ctx: Context, params: ERC721BurnParams) { - let ERC721BurnParams{token, caller, token_id } = params; - - assert(token == ctx.origin, 'ERC721: not authorized'); - - let owner = ERC721OwnerTrait::owner_of(ctx.world, token, token_id); - assert(!owner.is_zero(), 'ERC721: invalid token_id'); - - let is_approved_for_all = OperatorApprovalTrait::is_approved_for_all( - ctx.world, token, owner, caller - ); - let approved = ERC721TokenApprovalTrait::get_approved(ctx.world, token, token_id); - - assert( - owner == caller || is_approved_for_all || approved == caller, - 'ERC721: unauthorized caller' - ); - - ERC721BalanceTrait::decrease_balance(ctx.world, token, owner, 1); - ERC721OwnerTrait::set_owner(ctx.world, token, token_id, Zeroable::zero()); - - // emit events - super::emit_transfer(ctx.world, token, owner, Zeroable::zero(), token_id); - } -} - - -// TODO: move in erc_common - -#[system] -mod ERC721SetBaseUri { - use traits::Into; - use dojo::world::Context; - use dojo_erc::erc_common::components::{BaseUri, BaseUriTrait}; - use starknet::ContractAddress; - - fn execute(ctx: Context, token: ContractAddress, uri: felt252) { - assert(ctx.origin == token, 'ERC721: not authorized'); - BaseUriTrait::set_base_uri(ctx.world, token, uri); - // TODO: emit event - } -} diff --git a/crates/dojo-erc/src/erc_common.cairo b/crates/dojo-erc/src/erc_common.cairo deleted file mode 100644 index a6159592a7..0000000000 --- a/crates/dojo-erc/src/erc_common.cairo +++ /dev/null @@ -1,3 +0,0 @@ -mod components; -mod systems; -mod utils; diff --git a/crates/dojo-erc/src/erc_common/components.cairo b/crates/dojo-erc/src/erc_common/components.cairo deleted file mode 100644 index 2c764b6570..0000000000 --- a/crates/dojo-erc/src/erc_common/components.cairo +++ /dev/null @@ -1,5 +0,0 @@ -mod base_uri_component; -mod operator_approval_component; - -use base_uri_component::{base_uri, BaseUri, BaseUriTrait}; -use operator_approval_component::{operator_approval, OperatorApproval, OperatorApprovalTrait}; diff --git a/crates/dojo-erc/src/erc_common/components/base_uri_component.cairo b/crates/dojo-erc/src/erc_common/components/base_uri_component.cairo deleted file mode 100644 index b57e3e4f97..0000000000 --- a/crates/dojo-erc/src/erc_common/components/base_uri_component.cairo +++ /dev/null @@ -1,29 +0,0 @@ -use starknet::ContractAddress; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct BaseUri { - #[key] - token: ContractAddress, - uri: felt252 -} - -trait BaseUriTrait { - fn get_base_uri(world: IWorldDispatcher, token: ContractAddress) -> felt252; - fn set_base_uri(world: IWorldDispatcher, token: ContractAddress, new_base_uri: felt252); -} - -impl BaseUriImpl of BaseUriTrait { - fn get_base_uri(world: IWorldDispatcher, token: ContractAddress, ) -> felt252 { - let base_uri = get!(world, (token), BaseUri); - base_uri.uri - } - - // perform safety checks before calling this fn - fn set_base_uri(world: IWorldDispatcher, token: ContractAddress, new_base_uri: felt252) { - let mut base_uri = get!(world, (token), BaseUri); - base_uri.uri = new_base_uri; - set!(world, (base_uri)) - } -} - diff --git a/crates/dojo-erc/src/erc_common/components/operator_approval_component.cairo b/crates/dojo-erc/src/erc_common/components/operator_approval_component.cairo deleted file mode 100644 index 9ef366fc55..0000000000 --- a/crates/dojo-erc/src/erc_common/components/operator_approval_component.cairo +++ /dev/null @@ -1,55 +0,0 @@ -use starknet::ContractAddress; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct OperatorApproval { - #[key] - token: ContractAddress, - #[key] - owner: ContractAddress, - #[key] - operator: ContractAddress, - approved: bool -} - -trait OperatorApprovalTrait { - fn is_approved_for_all( - world: IWorldDispatcher, - token: ContractAddress, - account: ContractAddress, - operator: ContractAddress - ) -> bool; - - fn set_approval_for_all( - world: IWorldDispatcher, - token: ContractAddress, - owner: ContractAddress, - operator: ContractAddress, - approved: bool - ); -} - -impl OperatorApprovalImpl of OperatorApprovalTrait { - fn is_approved_for_all( - world: IWorldDispatcher, - token: ContractAddress, - account: ContractAddress, - operator: ContractAddress - ) -> bool { - let approval = get!(world, (token, account, operator), OperatorApproval); - approval.approved - } - - // perform safety checks before calling this fn - fn set_approval_for_all( - world: IWorldDispatcher, - token: ContractAddress, - owner: ContractAddress, - operator: ContractAddress, - approved: bool - ) { - let mut operator_approval = get!(world, (token, owner, operator), OperatorApproval); - operator_approval.approved = approved; - set!(world, (operator_approval)) - } -} diff --git a/crates/dojo-erc/src/erc_common/systems.cairo b/crates/dojo-erc/src/erc_common/systems.cairo deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/dojo-erc/src/erc_common/systems.cairo +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/dojo-erc/src/erc_common/utils.cairo b/crates/dojo-erc/src/erc_common/utils.cairo deleted file mode 100644 index 4b27236e30..0000000000 --- a/crates/dojo-erc/src/erc_common/utils.cairo +++ /dev/null @@ -1,30 +0,0 @@ -use serde::Serde; -use array::ArrayTrait; - -#[derive(Drop)] -struct ToCallData { - data: Array, -} - -#[generate_trait] -impl ToCallDataImpl of ToCallDataTrait { - fn plus, impl TD: Drop>( - mut self: ToCallData, data: T - ) -> ToCallData { - data.serialize(ref self.data); - self - } -} - -fn to_calldata, impl TD: Drop>(data: T) -> ToCallData { - let mut calldata: Array = ArrayTrait::new(); - data.serialize(ref calldata); - ToCallData { data: calldata } -} - - -fn system_calldata, impl TD: Drop>(data: T) -> Array { - let mut calldata: Array = ArrayTrait::new(); - data.serialize(ref calldata); - calldata -} diff --git a/crates/dojo-erc/src/lib.cairo b/crates/dojo-erc/src/lib.cairo index 9633ddbd5c..d72ce1149b 100644 --- a/crates/dojo-erc/src/lib.cairo +++ b/crates/dojo-erc/src/lib.cairo @@ -1,8 +1,15 @@ -mod erc20; -mod erc165; -mod erc721; -mod erc1155; -mod erc_common; +mod token { + mod erc20; + mod erc20_models; + mod erc721; + +} #[cfg(test)] -mod tests; +mod tests { + mod constants; + mod utils; + + mod erc20_tests; + mod erc721_tests; +} diff --git a/crates/dojo-erc/src/tests.cairo b/crates/dojo-erc/src/tests.cairo deleted file mode 100644 index 246121df8f..0000000000 --- a/crates/dojo-erc/src/tests.cairo +++ /dev/null @@ -1,6 +0,0 @@ -mod test_erc721; -mod test_erc721_utils; - -mod test_erc1155; -mod test_erc1155_utils; - diff --git a/crates/dojo-erc/src/tests/constants.cairo b/crates/dojo-erc/src/tests/constants.cairo new file mode 100644 index 0000000000..a66b6e7218 --- /dev/null +++ b/crates/dojo-erc/src/tests/constants.cairo @@ -0,0 +1,57 @@ +use starknet::ContractAddress; +use starknet::contract_address_const; + +const NAME: felt252 = 'NAME'; +const SYMBOL: felt252 = 'SYMBOL'; +const DECIMALS: u8 = 18_u8; +const SUPPLY: u256 = 2000; +const VALUE: u256 = 300; +const ROLE: felt252 = 'ROLE'; +const OTHER_ROLE: felt252 = 'OTHER_ROLE'; +const URI: felt252 = 'URI'; +const TOKEN_ID: u256 = 21; +const PUBKEY: felt252 = 'PUBKEY'; + +fn ADMIN() -> ContractAddress { + contract_address_const::<'ADMIN'>() +} + +fn AUTHORIZED() -> ContractAddress { + contract_address_const::<'AUTHORIZED'>() +} + +fn ZERO() -> ContractAddress { + contract_address_const::<0>() +} + +fn CALLER() -> ContractAddress { + contract_address_const::<'CALLER'>() +} + +fn OWNER() -> ContractAddress { + contract_address_const::<'OWNER'>() +} + +fn NEW_OWNER() -> ContractAddress { + contract_address_const::<'NEW_OWNER'>() +} + +fn OTHER() -> ContractAddress { + contract_address_const::<'OTHER'>() +} + +fn OTHER_ADMIN() -> ContractAddress { + contract_address_const::<'OTHER_ADMIN'>() +} + +fn SPENDER() -> ContractAddress { + contract_address_const::<'SPENDER'>() +} + +fn RECIPIENT() -> ContractAddress { + contract_address_const::<'RECIPIENT'>() +} + +fn OPERATOR() -> ContractAddress { + contract_address_const::<'OPERATOR'>() +} diff --git a/crates/dojo-erc/src/tests/erc20_tests.cairo b/crates/dojo-erc/src/tests/erc20_tests.cairo new file mode 100644 index 0000000000..669cfc7c4d --- /dev/null +++ b/crates/dojo-erc/src/tests/erc20_tests.cairo @@ -0,0 +1,546 @@ +use integer::BoundedInt; +use integer::u256; +use integer::u256_from_felt252; +use dojo_erc::tests::utils; +use dojo_erc::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, NAME, SYMBOL, DECIMALS, SUPPLY, VALUE +}; +use dojo_erc::token::erc20::ERC20::Approval; +use dojo_erc::token::erc20::ERC20::ERC20Impl; +use dojo_erc::token::erc20::ERC20::InternalImpl; +use dojo_erc::token::erc20::ERC20::Transfer; +use dojo_erc::token::erc20::ERC20; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::testing; +use zeroable::Zeroable; +use dojo::test_utils::spawn_test_world; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::token::erc20_models::{ + ERC20Allowance, erc_20_allowance, ERC20Balance, erc_20_balance, ERC20Meta, erc_20_meta +}; +use dojo_erc::token::erc20::ERC20::_worldContractMemberStateTrait; +use debug::PrintTrait; + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC20::ContractState) { + let world = spawn_test_world( + array![ + erc_20_allowance::TEST_CLASS_HASH, + erc_20_balance::TEST_CLASS_HASH, + erc_20_meta::TEST_CLASS_HASH, + ] + ); + let mut state = ERC20::contract_state_for_testing(); + state._world.write(world.contract_address); + (world, state) +} + +fn setup() -> ERC20::ContractState { + let (world, mut state) = STATE(); + ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); + utils::drop_event(ZERO()); + state +} + +// +// initializer & constructor +// + +#[test] +#[available_gas(25000000)] +fn test_initializer() { + let (world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL); + + assert(ERC20Impl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC20Impl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC20Impl::decimals(@state) == DECIMALS, 'Decimals should be 18'); + assert(ERC20Impl::total_supply(@state) == 0, 'Supply should eq 0'); +} + + +#[test] +#[available_gas(25000000)] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC20::constructor(ref state, world.contract_address, NAME, SYMBOL, SUPPLY, OWNER()); + + assert_only_event_transfer(ZERO(), OWNER(), SUPPLY); + + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq inital_supply'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq inital_supply'); + assert(ERC20Impl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC20Impl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC20Impl::decimals(@state) == DECIMALS, 'Decimals should be 18'); +} + +// +// Getters +// + +#[test] +#[available_gas(25000000)] +fn test_total_supply() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), SUPPLY); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Should eq SUPPLY'); +} + +#[test] +#[available_gas(25000000)] +fn test_balance_of() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), SUPPLY); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY, 'Should eq SUPPLY'); +} + + +#[test] +#[available_gas(25000000)] +fn test_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Should eq VALUE'); +} + +// +// approve & _approve +// + +#[test] +#[available_gas(25000000)] +fn test_approve() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + assert(ERC20Impl::approve(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test_approve_from_zero() { + let mut state = setup(); + ERC20Impl::approve(ref state, SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test_approve_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +fn test__approve() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + InternalImpl::_approve(ref state, OWNER(), SPENDER(), VALUE); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE, 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test__approve_from_zero() { + let mut state = setup(); + InternalImpl::_approve(ref state, Zeroable::zero(), SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test__approve_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + InternalImpl::_approve(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +// +// transfer & _transfer +// + +#[test] +#[available_gas(25000000)] +fn test_transfer() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + assert(ERC20Impl::transfer(ref state, RECIPIENT(), VALUE), 'Should return true'); + + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq VALUE'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - VALUE'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(25000000)] +fn test__transfer() { + let mut state = setup(); + + InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), VALUE); + + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Balance should eq amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test__transfer_not_enough_balance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + + let balance_plus_one = SUPPLY + 1; + InternalImpl::_transfer(ref state, OWNER(), RECIPIENT(), balance_plus_one); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: transfer from 0',))] +fn test__transfer_from_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, Zeroable::zero(), RECIPIENT(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test__transfer_to_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +// +// transfer_from +// + +#[test] +#[available_gas(30000000)] +fn test_transfer_from() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + testing::set_caller_address(SPENDER()); + assert(ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), VALUE), 'Should return true'); + + assert_event_approval(OWNER(), SPENDER(), 0); + assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); + + assert(ERC20Impl::balance_of(@state, RECIPIENT()) == VALUE, 'Should eq amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq suppy - amount'); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == 0, 'Should eq 0'); + assert(ERC20Impl::total_supply(@state) == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(25000000)] +fn test_transfer_from_doesnt_consume_infinite_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), BoundedInt::max()); + + testing::set_caller_address(SPENDER()); + ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), VALUE); + + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == BoundedInt::max(), + 'Allowance should not change' + ); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_transfer_from_greater_than_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + testing::set_caller_address(SPENDER()); + let allowance_plus_one = VALUE + 1; + ERC20Impl::transfer_from(ref state, OWNER(), RECIPIENT(), allowance_plus_one); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test_transfer_from_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + + testing::set_caller_address(SPENDER()); + ERC20Impl::transfer_from(ref state, OWNER(), Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_transfer_from_from_zero_address() { + let mut state = setup(); + ERC20Impl::transfer_from(ref state, Zeroable::zero(), RECIPIENT(), VALUE); +} + +// +// increase_allowance & increaseAllowance +// + +#[test] +#[available_gas(25000000)] +fn test_increase_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::increase_allowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), VALUE * 2); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test_increase_allowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::increase_allowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test_increase_allowance_from_zero_address() { + let mut state = setup(); + ERC20::increase_allowance(ref state, SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +fn test_increaseAllowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::increaseAllowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), 2 * VALUE); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve to 0',))] +fn test_increaseAllowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::increaseAllowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: approve from 0',))] +fn test_increaseAllowance_from_zero_address() { + let mut state = setup(); + ERC20::increaseAllowance(ref state, SPENDER(), VALUE); +} + +// +// decrease_allowance & decreaseAllowance +// + +#[test] +#[available_gas(25000000)] +fn test_decrease_allowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::decrease_allowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), 0); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decrease_allowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::decrease_allowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decrease_allowance_from_zero_address() { + let mut state = setup(); + ERC20::decrease_allowance(ref state, SPENDER(), VALUE); +} + +#[test] +#[available_gas(25000000)] +fn test_decreaseAllowance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20Impl::approve(ref state, SPENDER(), VALUE); + utils::drop_event(ZERO()); + + assert(ERC20::decreaseAllowance(ref state, SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(OWNER(), SPENDER(), 0); + assert(ERC20Impl::allowance(@state, OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decreaseAllowance_to_zero_address() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC20::decreaseAllowance(ref state, Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_decreaseAllowance_from_zero_address() { + let mut state = setup(); + ERC20::decreaseAllowance(ref state, SPENDER(), VALUE); +} + +// +// _spend_allowance +// + +#[test] +#[available_gas(25000000)] +fn test__spend_allowance_not_unlimited() { + let mut state = setup(); + + InternalImpl::_approve(ref state, OWNER(), SPENDER(), SUPPLY); + utils::drop_event(ZERO()); + + InternalImpl::_spend_allowance(ref state, OWNER(), SPENDER(), VALUE); + + assert_only_event_approval(OWNER(), SPENDER(), SUPPLY - VALUE); + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == SUPPLY - VALUE, + 'Should eq supply - amount' + ); +} + +#[test] +#[available_gas(25000000)] +fn test__spend_allowance_unlimited() { + let mut state = setup(); + InternalImpl::_approve(ref state, OWNER(), SPENDER(), BoundedInt::max()); + + let max_minus_one: u256 = BoundedInt::max() - 1; + InternalImpl::_spend_allowance(ref state, OWNER(), SPENDER(), max_minus_one); + + assert( + ERC20Impl::allowance(@state, OWNER(), SPENDER()) == BoundedInt::max(), + 'Allowance should not change' + ); +} + +// +// _mint +// + +#[test] +#[available_gas(25000000)] +fn test__mint() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, OWNER(), VALUE); + assert_only_event_transfer(ZERO(), OWNER(), VALUE); + assert(ERC20Impl::balance_of(@state, OWNER()) == VALUE, 'Should eq amount'); + assert(ERC20Impl::total_supply(@state) == VALUE, 'Should eq total supply'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: mint to 0',))] +fn test__mint_to_zero() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, Zeroable::zero(), VALUE); +} + +// +// _burn +// + +#[test] +#[available_gas(25000000)] +fn test__burn() { + let mut state = setup(); + InternalImpl::_burn(ref state, OWNER(), VALUE); + + assert_only_event_transfer(OWNER(), ZERO(), VALUE); + assert(ERC20Impl::total_supply(@state) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(ERC20Impl::balance_of(@state, OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); +} + +#[test] +#[available_gas(25000000)] +#[should_panic(expected: ('ERC20: burn from 0',))] +fn test__burn_from_zero() { + let mut state = setup(); + InternalImpl::_burn(ref state, Zeroable::zero(), VALUE); +} + +// +// Helpers +// + +fn assert_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.spender == spender, 'Invalid `spender`'); + assert(event.value == value, 'Invalid `value`'); +} + +fn assert_only_event_approval(owner: ContractAddress, spender: ContractAddress, value: u256) { + assert_event_approval(owner, spender, value); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.value == value, 'Invalid `value`'); +} + +fn assert_only_event_transfer(from: ContractAddress, to: ContractAddress, value: u256) { + assert_event_transfer(from, to, value); + utils::assert_no_events_left(ZERO()); +} diff --git a/crates/dojo-erc/src/tests/erc721_tests.cairo b/crates/dojo-erc/src/tests/erc721_tests.cairo new file mode 100644 index 0000000000..a4c194eddb --- /dev/null +++ b/crates/dojo-erc/src/tests/erc721_tests.cairo @@ -0,0 +1,1454 @@ +use integer::BoundedInt; +use integer::u256; +use integer::u256_from_felt252; +use dojo_erc::tests::utils; +use dojo_erc::tests::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, OPERATOR, OTHER, NAME, SYMBOL, URI, TOKEN_ID +}; + +use dojo_erc::token::erc721::ERC721::ERC721Impl; +use dojo_erc::token::erc721::ERC721::ERC721CamelOnlyImpl; +use dojo_erc::token::erc721::ERC721::ERC721MetadataImpl; +use dojo_erc::token::erc721::ERC721::InternalImpl; +use dojo_erc::token::erc721::ERC721::WorldInteractionsImpl; +use dojo_erc::token::erc721::ERC721::{Approval, ApprovalForAll, Transfer}; +use dojo_erc::token::erc721::ERC721; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::testing; +use zeroable::Zeroable; +use dojo::test_utils::spawn_test_world; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +use dojo_erc::token::erc721::models::{ + ERC721Meta, erc_721_meta, ERC721OperatorApproval, erc_721_operator_approval, ERC721Owner, + erc_721_owner, ERC721Balance, erc_721_balance, ERC721TokenApproval, erc_721_token_approval +}; +use dojo_erc::token::erc721::ERC721::_worldContractMemberStateTrait; +use debug::PrintTrait; + +// +// Setup +// + +fn STATE() -> (IWorldDispatcher, ERC721::ContractState) { + let world = spawn_test_world( + array![ + erc_721_meta::TEST_CLASS_HASH, + erc_721_operator_approval::TEST_CLASS_HASH, + erc_721_owner::TEST_CLASS_HASH, + erc_721_balance::TEST_CLASS_HASH, + erc_721_token_approval::TEST_CLASS_HASH, + ] + ); + let mut state = ERC721::contract_state_for_testing(); + state._world.write(world.contract_address); + (world, state) +} + +fn setup() -> ERC721::ContractState { + let (world, mut state) = STATE(); + ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); + utils::drop_event(ZERO()); + state +} + +// fn setup_receiver() -> ContractAddress { +// utils::deploy(ERC721Receiver::TEST_CLASS_HASH, array![]) +// } + +// fn setup_camel_receiver() -> ContractAddress { +// utils::deploy(CamelERC721ReceiverMock::TEST_CLASS_HASH, array![]) +// } + +// fn setup_account() -> ContractAddress { +// let mut calldata = array![PUBKEY]; +// utils::deploy(Account::TEST_CLASS_HASH, calldata) +// } + +// fn setup_camel_account() -> ContractAddress { +// let mut calldata = array![PUBKEY]; +// utils::deploy(CamelAccountMock::TEST_CLASS_HASH, calldata) +// } + +// +// initializer & constructor +// + +#[test] +#[available_gas(20000000)] +fn test_constructor() { + let (world, mut state) = STATE(); + ERC721::constructor(ref state, world.contract_address, NAME, SYMBOL, URI, OWNER(), TOKEN_ID); + + assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance should be one'); + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'OWNER should be owner'); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + +#[test] +#[available_gas(10000000)] +fn test_initializer() { + let (world, mut state) = STATE(); + InternalImpl::initializer(ref state, NAME, SYMBOL, URI); + + assert(ERC721MetadataImpl::name(@state) == NAME, 'Name should be NAME'); + assert(ERC721MetadataImpl::symbol(@state) == SYMBOL, 'Symbol should be SYMBOL'); + + assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance should be zero'); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_ID), 'Missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, erc721::interface::IERC721_METADATA_ID), +// 'missing interface ID' +// ); +// assert( +// SRC5Impl::supports_interface(@state, introspection::interface::ISRC5_ID), +// 'missing interface ID' +// ); +} + + +// +// Getters +// + +#[test] +#[available_gas(20000000)] +fn test_balance_of() { + let state = setup(); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Should return balance'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid account',))] +fn test_balance_of_zero() { + let state = setup(); + ERC721Impl::balance_of(@state, ZERO()); +} + +#[test] +#[available_gas(20000000)] +fn test_owner_of() { + let state = setup(); + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Should return owner'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_owner_of_non_minted() { + let state = setup(); + ERC721Impl::owner_of(@state, u256_from_felt252(7)); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_token_uri_non_minted() { + let state = setup(); + ERC721MetadataImpl::token_uri(@state, u256_from_felt252(7)); +} + +#[test] +#[available_gas(20000000)] +fn test_get_approved() { + let mut state = setup(); + let spender = SPENDER(); + let token_id = TOKEN_ID; + + assert(ERC721Impl::get_approved(@state, token_id) == ZERO(), 'Should return non-approval'); + InternalImpl::_approve(ref state, spender, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == spender, 'Should return approval'); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_get_approved_nonexistent() { + let mut state = setup(); + ERC721Impl::get_approved(@state, u256_from_felt252(7)); +} + +#[test] +#[available_gas(20000000)] +fn test__exists() { + let (world, mut state) = STATE(); + let token_id = TOKEN_ID; + + assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' + ); + + InternalImpl::_mint(ref state, RECIPIENT(), token_id); + + assert(InternalImpl::_exists(@state, token_id), 'Token should exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == RECIPIENT(), + 'Invalid owner' + ); + + InternalImpl::_burn(ref state, token_id); + + assert(!InternalImpl::_exists(@state, token_id), 'Token should not exist'); + assert( + WorldInteractionsImpl::get_owner_of(@state, token_id).address == ZERO(), 'Invalid owner' + ); +} + + +// +// approve & _approve +// + +#[test] +#[available_gas(20000000)] +fn test_approve_from_owner() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +fn test_approve_from_operator() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_approve_from_unauthorized() { + let mut state = setup(); + + testing::set_caller_address(OTHER()); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: approval to owner',))] +fn test_approve_to_owner() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721Impl::approve(ref state, OWNER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_approve_nonexistent() { + // let mut state = STATE(); + let (world, mut state) = STATE(); + ERC721Impl::approve(ref state, SPENDER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +fn test__approve() { + let mut state = setup(); + InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); + assert_event_approval(OWNER(), SPENDER(), TOKEN_ID); + + assert( + ERC721Impl::get_approved(@state, TOKEN_ID) == SPENDER(), 'Spender not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: approval to owner',))] +fn test__approve_to_owner() { + let mut state = setup(); + InternalImpl::_approve(ref state, OWNER(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__approve_nonexistent() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + InternalImpl::_approve(ref state, SPENDER(), TOKEN_ID); +} + +// +// set_approval_for_all & _set_approval_for_all +// + +#[test] +#[available_gas(20000000)] +fn test_set_approval_for_all() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + + assert(!ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Approval not revoked correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_true() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test_set_approval_for_all_owner_equal_operator_false() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + testing::set_caller_address(OWNER()); + ERC721Impl::set_approval_for_all(ref state, OWNER(), false); +} + +#[test] +#[available_gas(20000000)] +fn test__set_approval_for_all() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + assert(!ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), 'Invalid default value'); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), true); + assert_event_approval_for_all(OWNER(), OPERATOR(), true); + + assert( + ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); + + InternalImpl::_set_approval_for_all(ref state, OWNER(), OPERATOR(), false); + assert_event_approval_for_all(OWNER(), OPERATOR(), false); + + assert( + !ERC721Impl::is_approved_for_all(@state, OWNER(), OPERATOR()), + 'Operator not approved correctly' + ); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_true() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), true); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: self approval',))] +fn test__set_approval_for_all_owner_equal_operator_false() { + // let mut state = STATE(); + let (world, mut state) = STATE(); + InternalImpl::_set_approval_for_all(ref state, OWNER(), OWNER(), false); +} + + +// +// transfer_from & transferFrom +// + +#[test] +#[available_gas(60000000)] +fn test_transfer_from_owner() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + // set approval to check reset + InternalImpl::_approve(ref state, OTHER(), token_id); + utils::drop_event(ZERO()); + + assert_state_before_transfer(@state, owner, recipient, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); + + testing::set_caller_address(owner); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_owner() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + // set approval to check reset + InternalImpl::_approve(ref state, OTHER(), token_id); + utils::drop_event(ZERO()); + + assert_state_before_transfer(@state, owner, recipient, token_id); + assert(ERC721Impl::get_approved(@state, token_id) == OTHER(), 'Approval not implicitly reset'); + + testing::set_caller_address(owner); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_transfer_from_nonexistent() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + ERC721Impl::transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test_transferFrom_nonexistent() { + //let mut state = STATE(); + let (world, mut state) = STATE(); + ERC721CamelOnlyImpl::transferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test_transfer_from_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + ERC721Impl::transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test_transferFrom_to_zero() { + let mut state = setup(); + + testing::set_caller_address(OWNER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_to_owner() { + let mut state = setup(); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + + testing::set_caller_address(OWNER()); + ERC721Impl::transfer_from(ref state, OWNER(), OWNER(), TOKEN_ID); + assert_event_transfer(OWNER(), OWNER(), TOKEN_ID); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_to_owner() { + let mut state = setup(); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + + testing::set_caller_address(OWNER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), OWNER(), TOKEN_ID); + assert_event_transfer( OWNER(), OWNER(), TOKEN_ID); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership after'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner after'); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_approved() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::approve(ref state, OPERATOR(), token_id); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_approved() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + assert_state_before_transfer(@state,owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::approve(ref state, OPERATOR(), token_id); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state,owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transfer_from_approved_for_all() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721Impl::transfer_from(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(50000000)] +fn test_transferFrom_approved_for_all() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state,owner, recipient, token_id); + + testing::set_caller_address(owner); + ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); + utils::drop_event(ZERO()); + + testing::set_caller_address(OPERATOR()); + ERC721CamelOnlyImpl::transferFrom(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state,owner, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_transfer_from_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC721Impl::transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: unauthorized caller',))] +fn test_transferFrom_unauthorized() { + let mut state = setup(); + testing::set_caller_address(OTHER()); + ERC721CamelOnlyImpl::transferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID); +} + +// // +// // safe_transfer_from & safeTransferFrom +// // + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_account() { +// let mut state = setup(); +// let account = setup_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_account() { +// let mut state = setup(); +// let account = setup_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_account_camel() { +// let mut state = setup(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_account_camel() { +// let mut state = setup(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, account, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, account, token_id, DATA(true)); +// assert_event_transfer(owner, account, token_id); + +// assert_state_after_transfer(@state,owner, account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_receiver() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_receiver() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_receiver_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_receiver_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safe_transfer_from_to_receiver_failure() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safeTransferFrom_to_receiver_failure() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safe_transfer_from_to_receiver_failure_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe transfer failed',))] +// fn test_safeTransferFrom_to_receiver_failure_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(false)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test_safe_transfer_from_to_non_receiver() { +// let mut state = setup(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, recipient, token_id, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test_safeTransferFrom_to_non_receiver() { +// let mut state = setup(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, recipient, token_id, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test_safe_transfer_from_nonexistent() { +// let mut state = STATE(); +// ERC721Impl::safe_transfer_from(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test_safeTransferFrom_nonexistent() { +// let mut state = STATE(); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test_safe_transfer_from_to_zero() { +// let mut state = setup(); +// testing::set_caller_address(OWNER()); +// ERC721Impl::safe_transfer_from(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test_safeTransferFrom_to_zero() { +// let mut state = setup(); +// testing::set_caller_address(OWNER()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_owner() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_owner() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_to_owner_camel() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_camel_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721Impl::safe_transfer_from(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_to_owner_camel() { +// let mut state = STATE(); +// let token_id = TOKEN_ID; +// let owner = setup_camel_receiver(); +// InternalImpl::initializer(ref state, NAME, SYMBOL); +// InternalImpl::_mint(ref state, owner, token_id); +// utils::drop_event(ZERO()); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership before'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner before'); + +// testing::set_caller_address(owner); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, owner, token_id, DATA(true)); +// assert_event_transfer(owner, owner, token_id); + +// assert(ERC721Impl::owner_of(@state, token_id) == owner, 'Ownership after'); +// assert(ERC721Impl::balance_of(@state, owner) == 1, 'Balance of owner after'); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::approve(ref state, OPERATOR(), token_id); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved_for_all() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved_for_all() { +// let mut state = setup(); +// let receiver = setup_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safe_transfer_from_approved_for_all_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721Impl::safe_transfer_from(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test_safeTransferFrom_approved_for_all_camel() { +// let mut state = setup(); +// let receiver = setup_camel_receiver(); +// let token_id = TOKEN_ID; +// let owner = OWNER(); + +// assert_state_before_transfer(@state,owner, receiver, token_id); + +// testing::set_caller_address(owner); +// ERC721Impl::set_approval_for_all(ref state, OPERATOR(), true); +// utils::drop_event(ZERO()); + +// testing::set_caller_address(OPERATOR()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, owner, receiver, token_id, DATA(true)); +// assert_event_transfer(owner, receiver, token_id); + +// assert_state_after_transfer(@state,owner, receiver, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: unauthorized caller',))] +// fn test_safe_transfer_from_unauthorized() { +// let mut state = setup(); +// testing::set_caller_address(OTHER()); +// ERC721Impl::safe_transfer_from(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: unauthorized caller',))] +// fn test_safeTransferFrom_unauthorized() { +// let mut state = setup(); +// testing::set_caller_address(OTHER()); +// ERC721CamelOnlyImpl::safeTransferFrom(ref state, OWNER(), RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// +// _transfer +// + +#[test] +#[available_gas(50000000)] +fn test__transfer() { + let mut state = setup(); + let token_id = TOKEN_ID; + let owner = OWNER(); + let recipient = RECIPIENT(); + + assert_state_before_transfer(@state, owner, recipient, token_id); + + InternalImpl::_transfer(ref state, owner, recipient, token_id); + assert_event_transfer(owner, recipient, token_id); + + assert_state_after_transfer(@state, owner, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__transfer_nonexistent() { + let (world, mut state) = STATE(); + InternalImpl::_transfer(ref state, ZERO(), RECIPIENT(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test__transfer_to_zero() { + let mut state = setup(); + InternalImpl::_transfer(ref state, OWNER(), ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: wrong sender',))] +fn test__transfer_from_invalid_owner() { + let mut state = setup(); + InternalImpl::_transfer(ref state, RECIPIENT(), OWNER(), TOKEN_ID); +} + +// +// _mint +// + +#[test] +#[available_gas(20000000)] +fn test__mint() { + let (world, mut state) = STATE(); + let recipient = RECIPIENT(); + let token_id = TOKEN_ID; + + assert_state_before_mint(@state, recipient); + InternalImpl::_mint(ref state, recipient, TOKEN_ID); + assert_event_transfer(ZERO(), recipient, token_id); + + assert_state_after_mint(@state, recipient, token_id); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: invalid receiver',))] +fn test__mint_to_zero() { + let (world, mut state) = STATE(); + InternalImpl::_mint(ref state, ZERO(), TOKEN_ID); +} + +#[test] +#[available_gas(20000000)] +#[should_panic(expected: ('ERC721: token already minted',))] +fn test__mint_already_exist() { + let mut state = setup(); + InternalImpl::_mint(ref state, RECIPIENT(), TOKEN_ID); +} + +// // +// // _safe_mint +// // + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_receiver() { +// let mut state = STATE(); +// let recipient = setup_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_event_transfer(ZERO(), recipient, token_id); + +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_receiver_camel() { +// let mut state = STATE(); +// let recipient = setup_camel_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_event_transfer(ZERO(), recipient, token_id); + +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_account() { +// let mut state = STATE(); +// let account = setup_account(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,account); +// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); +// assert_event_transfer(ZERO(), account, token_id); + +// assert_state_after_mint(@state,account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// fn test__safe_mint_to_account_camel() { +// let mut state = STATE(); +// let account = setup_camel_account(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,account); +// InternalImpl::_safe_mint(ref state, account, token_id, DATA(true)); +// assert_event_transfer(ZERO(), account, token_id); + +// assert_state_after_mint(@state,account, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +// fn test__safe_mint_to_non_receiver() { +// let mut state = STATE(); +// let recipient = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, array![]); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(true)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe mint failed',))] +// fn test__safe_mint_to_receiver_failure() { +// let mut state = STATE(); +// let recipient = setup_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: safe mint failed',))] +// fn test__safe_mint_to_receiver_failure_camel() { +// let mut state = STATE(); +// let recipient = setup_camel_receiver(); +// let token_id = TOKEN_ID; + +// assert_state_before_mint(@state,recipient); +// InternalImpl::_safe_mint(ref state, recipient, token_id, DATA(false)); +// assert_state_after_mint(@state,recipient, token_id); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid receiver',))] +// fn test__safe_mint_to_zero() { +// let mut state = STATE(); +// InternalImpl::_safe_mint(ref state, ZERO(), TOKEN_ID, DATA(true)); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: token already minted',))] +// fn test__safe_mint_already_exist() { +// let mut state = setup(); +// InternalImpl::_safe_mint(ref state, RECIPIENT(), TOKEN_ID, DATA(true)); +// } + +// +// _burn +// + +#[test] +#[available_gas(25000000)] +fn test__burn() { + let mut state = setup(); + + InternalImpl::_approve(ref state, OTHER(), TOKEN_ID); + utils::drop_event(ZERO()); + + assert(ERC721Impl::owner_of(@state, TOKEN_ID) == OWNER(), 'Ownership before'); + assert(ERC721Impl::balance_of(@state, OWNER()) == 1, 'Balance of owner before'); + assert(ERC721Impl::get_approved(@state, TOKEN_ID) == OTHER(), 'Approval before'); + + InternalImpl::_burn(ref state, TOKEN_ID); + assert_event_transfer(OWNER(), ZERO(), TOKEN_ID); + + assert( + WorldInteractionsImpl::get_owner_of(@state, TOKEN_ID).address == ZERO(), 'Ownership after' + ); + assert(ERC721Impl::balance_of(@state, OWNER()) == 0, 'Balance of owner after'); + assert( + WorldInteractionsImpl::get_token_approval(@state, TOKEN_ID).address == ZERO(), + 'Approval after' + ); +} + +#[test] +#[available_gas(30000000)] +#[should_panic(expected: ('ERC721: invalid token ID',))] +fn test__burn_nonexistent() { + let (mut world, mut state) = STATE(); + InternalImpl::_burn(ref state, TOKEN_ID); +} + +// +// _set_token_uri +// + +// #[test] +// #[available_gas(20000000)] +// fn test__set_token_uri() { +// let mut state = setup(); + +// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == 0, 'URI should be 0'); +// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); +// assert(ERC721MetadataImpl::token_uri(@state, TOKEN_ID) == URI, 'URI should be set'); +// } + +// #[test] +// #[available_gas(20000000)] +// #[should_panic(expected: ('ERC721: invalid token ID',))] +// fn test__set_token_uri_nonexistent() { +// let mut state = STATE(); +// InternalImpl::_set_token_uri(ref state, TOKEN_ID, URI); +// } + +// +// Helpers +// + +fn assert_state_before_transfer( + state: @ERC721::ContractState, + owner: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == owner, 'Ownership before'); + assert(ERC721Impl::balance_of(state, owner) == 1, 'Balance of owner before'); + assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_transfer( + state: @ERC721::ContractState, + owner: ContractAddress, + recipient: ContractAddress, + token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); + assert(ERC721Impl::balance_of(state, owner) == 0, 'Balance of owner after'); + assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); + assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval not implicitly reset'); +} + +fn assert_state_before_mint(state: @ERC721::ContractState, recipient: ContractAddress) { + assert(ERC721Impl::balance_of(state, recipient) == 0, 'Balance of recipient before'); +} + +fn assert_state_after_mint( + state: @ERC721::ContractState, recipient: ContractAddress, token_id: u256 +) { + assert(ERC721Impl::owner_of(state, token_id) == recipient, 'Ownership after'); + assert(ERC721Impl::balance_of(state, recipient) == 1, 'Balance of recipient after'); + assert(ERC721Impl::get_approved(state, token_id) == ZERO(), 'Approval implicitly set'); +} + +fn assert_event_approval_for_all( + owner: ContractAddress, operator: ContractAddress, approved: bool +) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.operator == operator, 'Invalid `operator`'); + assert(event.approved == approved, 'Invalid `approved`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_approval(owner: ContractAddress, approved: ContractAddress, token_id: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.approved == approved, 'Invalid `approved`'); + assert(event.token_id == token_id, 'Invalid `token_id`'); + utils::assert_no_events_left(ZERO()); +} + +fn assert_event_transfer(from: ContractAddress, to: ContractAddress, token_id: u256) { + let event = utils::pop_log::(ZERO()).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.token_id == token_id, 'Invalid `token_id`'); + utils::assert_no_events_left(ZERO()); +} diff --git a/crates/dojo-erc/src/tests/test_erc1155.cairo b/crates/dojo-erc/src/tests/test_erc1155.cairo deleted file mode 100644 index 44d3ffc3ed..0000000000 --- a/crates/dojo-erc/src/tests/test_erc1155.cairo +++ /dev/null @@ -1,503 +0,0 @@ -use zeroable::Zeroable; -use traits::{Into, Default, IndexView}; -use array::ArrayTrait; -use serde::Serde; -use starknet::ContractAddress; -use starknet::testing::set_contract_address; - -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::tests::test_erc1155_utils::{ - spawn_world, deploy_erc1155, deploy_default, deploy_testcase1, ZERO, USER1, USER2, DEPLOYER, - PROXY -}; - -use dojo_erc::erc165::interface::IERC165_ID; -use dojo_erc::erc1155::interface::{ - IERC1155A, IERC1155ADispatcher, IERC1155ADispatcherTrait, IERC1155_ID, IERC1155_METADATA_ID, - IERC1155_RECEIVER_ID -}; - - -#[test] -#[available_gas(30000000)] -fn test_deploy() { - let world = spawn_world(); - let erc1155_address = deploy_erc1155(world, DEPLOYER(), 'uri', 'seed-42'); - let erc1155 = IERC1155ADispatcher { contract_address: erc1155_address }; - assert(erc1155.owner() == DEPLOYER(), 'invalid owner'); -} - -#[test] -#[available_gas(30000000)] -fn test_deploy_default() { - let (world, erc1155) = deploy_default(); - assert(erc1155.owner() == DEPLOYER(), 'invalid owner'); -} - - -// -// supports_interface -// - -#[test] -#[available_gas(30000000)] -fn test_should_support_interfaces() { - let (world, erc1155) = deploy_default(); - - assert(erc1155.supports_interface(IERC165_ID) == true, 'should support erc165'); - assert(erc1155.supports_interface(IERC1155_ID) == true, 'should support erc1155'); - assert( - erc1155.supports_interface(IERC1155_METADATA_ID) == true, 'should support erc1155_metadata' - ); -} - -// -// uri -// - -#[test] -#[available_gas(30000000)] -fn test_uri() { - let (world, erc1155) = deploy_default(); - assert(erc1155.uri(64) == 'uri', 'invalid uri'); -} - - -// -// behaves like an ERC1155 -// - -// -// balance_of -// -#[test] -#[available_gas(30000000)] -#[should_panic(expected: ('ERC1155: invalid owner address', 'ENTRYPOINT_FAILED', ))] -fn test_balance_of_zero_address() { - //reverts when queried about the zero address - - let (world, erc1155) = deploy_default(); - erc1155.balance_of(ZERO(), 0); // should panic -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_empty_balance() { - // when accounts don't own tokens - // returns zero for given addresses - let (world, erc1155) = deploy_default(); - assert(erc1155.balance_of(USER1(), 0) == 0, 'should be 0'); - assert(erc1155.balance_of(USER1(), 69) == 0, 'should be 0'); - assert(erc1155.balance_of(USER2(), 0) == 0, 'should be 0'); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_with_tokens() { - // when accounts own some tokens - // returns the amount of tokens owned by the given addresses - let (world, erc1155) = deploy_default(); - - erc1155.mint(USER1(), 0, 1, array![]); - erc1155.mint(USER1(), 69, 42, array![]); - erc1155.mint(USER2(), 69, 5, array![]); - - assert(erc1155.balance_of(USER1(), 0) == 1, 'should be 1'); - assert(erc1155.balance_of(USER1(), 69) == 42, 'should be 42'); - assert(erc1155.balance_of(USER2(), 69) == 5, 'should be 5'); -} - -// -// balance_of_batch -// - -#[test] -#[available_gas(30000000)] -#[should_panic(expected: ('ERC1155: invalid length', 'ENTRYPOINT_FAILED', ))] -fn test_balance_of_batch_with_invalid_input() { - // reverts when input arrays don't match up - let (world, erc1155) = deploy_default(); - erc1155.balance_of_batch(array![USER1(), USER2()], array![0]); - erc1155.balance_of_batch(array![USER1()], array![0, 1, 2]); -} - -#[test] -#[available_gas(30000000)] -#[should_panic(expected: ('ERC1155: invalid owner address', 'ENTRYPOINT_FAILED', ))] -fn test_balance_of_batch_address_zero() { - // reverts when input arrays don't match up - let (world, erc1155) = deploy_default(); - erc1155.balance_of_batch(array![USER1(), ZERO()], array![0, 1]); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_batch_empty_account() { - // when accounts don't own tokens - // returns zeros for each account - let (world, erc1155) = deploy_default(); - let balances = erc1155.balance_of_batch(array![USER1(), USER1(), USER1()], array![0, 1, 5]); - let bals = @balances; - assert(balances.len() == 3, 'should be 3'); - assert(bals[0] == @0_u256, 'should be 0'); - assert(bals[1] == @0_u256, 'should be 0'); - assert(bals[2] == @0_u256, 'should be 0'); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_batch_with_tokens() { - // when accounts own some tokens - // returns amounts owned by each account in order passed - let (world, erc1155) = deploy_default(); - - erc1155.mint(USER1(), 0, 1, array![]); - erc1155.mint(USER1(), 69, 42, array![]); - erc1155.mint(USER2(), 69, 2, array![]); - - let balances = erc1155.balance_of_batch(array![USER1(), USER1(), USER2()], array![0, 69, 69]); - let bals = @balances; - assert(balances.len() == 3, 'should be 3'); - assert(bals[0] == @1_u256, 'should be 1'); - assert(bals[1] == @42_u256, 'should be 42'); - assert(bals[2] == @2_u256, 'should be 2'); -} - -#[test] -#[available_gas(30000000)] -fn test_balance_of_batch_with_tokens_2() { - // when accounts own some tokens - // returns multiple times the balance of the same address when asked - let (world, erc1155) = deploy_default(); - - erc1155.mint(USER1(), 0, 1, array![]); - erc1155.mint(USER2(), 69, 2, array![]); - - let balances = erc1155.balance_of_batch(array![USER1(), USER2(), USER1()], array![0, 69, 0]); - let bals = @balances; - assert(balances.len() == 3, 'should be 3'); - assert(bals[0] == @1_u256, 'should be 1'); - assert(bals[1] == @2_u256, 'should be 2'); - assert(bals[2] == @1_u256, 'should be 1'); -} - - -// -// balance_of_batch -// - -#[test] -#[available_gas(30000000)] -fn test_set_approval_for_all() { - // sets approval status which can be queried via is_approved_for_all - let (world, erc1155) = deploy_default(); - // impersonate user1 - set_contract_address(USER1()); - - erc1155.set_approval_for_all(PROXY(), true); - assert(erc1155.is_approved_for_all(USER1(), PROXY()) == true, 'should be true'); -} - -#[test] -#[available_gas(30000000)] -fn test_set_unset_approval_for_all() { - // sets approval status which can be queried via is_approved_for_all - let (world, erc1155) = deploy_default(); - // impersonate user1 - set_contract_address(USER1()); - erc1155.set_approval_for_all(PROXY(), true); - assert(erc1155.is_approved_for_all(USER1(), PROXY()) == true, 'should be true'); - erc1155.set_approval_for_all(PROXY(), false); - assert(erc1155.is_approved_for_all(USER1(), PROXY()) == false, 'should be false'); -} - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_set_approval_for_all_on_self() { - // reverts if attempting to approve self as an operator - let (world, erc1155) = deploy_default(); - // impersonate user1 - set_contract_address(USER1()); - erc1155.set_approval_for_all(USER1(), true); // should panic -} - -// -// safe_transfer_from -// - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_safe_transfer_from() { - // reverts when transferring more than balance - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc1155.safe_transfer_from(USER1(), USER2(), 1, 999, array![]); // should panic -} - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_safe_transfer_to_zero() { - // reverts when transferring to zero address - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc1155.safe_transfer_from(USER1(), ZERO(), 1, 1, array![]); // should panic -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_debit_sender() { - // debits transferred balance from sender - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let balance_before = erc1155.balance_of(USER1(), 1); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); - let balance_after = erc1155.balance_of(USER1(), 1); - - assert(balance_after == balance_before - 1, 'invalid balance after'); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_credit_receiver() { - // credits transferred balance to receiver - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let balance_before = erc1155.balance_of(USER2(), 1); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); - let balance_after = erc1155.balance_of(USER2(), 1); - - assert(balance_after == balance_before + 1, 'invalid balance after'); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_preserve_existing_balances() { - // preserves existing balances which are not transferred by multiTokenHolder - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let balance_before_2 = erc1155.balance_of(USER2(), 2); - let balance_before_3 = erc1155.balance_of(USER2(), 3); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); - let balance_after_2 = erc1155.balance_of(USER2(), 2); - let balance_after_3 = erc1155.balance_of(USER2(), 3); - - assert(balance_after_2 == balance_before_2, 'should be equal'); - assert(balance_after_3 == balance_before_3, 'should be equal'); -} - -#[test] -#[available_gas(30000000)] -#[should_panic()] -fn test_safe_transfer_from_unapproved_operator() { - // when called by an operator on behalf of the multiTokenHolder - // when operator is not approved by multiTokenHolder - - let (world, erc1155) = deploy_testcase1(); - - // impersonate user2 - set_contract_address(USER2()); - - erc1155.safe_transfer_from(USER1(), USER2(), 1, 1, array![]); // should panic -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_from_approved_operator() { - // when called by an operator on behalf of the multiTokenHolder - // when operator is approved by multiTokenHolder - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(PROXY()); - - let balance_before = erc1155.balance_of(USER1(), 1); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 2, array![]); - let balance_after = erc1155.balance_of(USER1(), 1); - - assert(balance_after == balance_before - 2, 'invalid balance'); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_transfer_from_approved_operator_preserve_operator_balance() { - // when called by an operator on behalf of the multiTokenHolder - // preserves operator's balances not involved in the transfer - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(PROXY()); - - let balance_before_1 = erc1155.balance_of(PROXY(), 1); - let balance_before_2 = erc1155.balance_of(PROXY(), 2); - let balance_before_3 = erc1155.balance_of(PROXY(), 3); - erc1155.safe_transfer_from(USER1(), USER2(), 1, 2, array![]); - let balance_after_1 = erc1155.balance_of(PROXY(), 1); - let balance_after_2 = erc1155.balance_of(PROXY(), 2); - let balance_after_3 = erc1155.balance_of(PROXY(), 3); - - assert(balance_before_1 == balance_after_1, 'should be equal'); - assert(balance_before_2 == balance_after_2, 'should be equal'); - assert(balance_before_3 == balance_after_3, 'should be equal'); -} - - -// -// safe_batch_transfer_from -// - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_more_than_balance() { - // reverts when transferring amount more than any of balances - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 999, 1], array![]); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_mismatching_array_len() { - // reverts when ids array length doesn't match amounts array length - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 1], array![]); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_to_zero_address() { - // reverts when transferring to zero address - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc1155.safe_batch_transfer_from(USER1(), ZERO(), array![1, 2], array![1, 1], array![]); -} - - -#[test] -#[available_gas(50000000)] -fn test_safe_batch_transfer_from_debits_sender() { - // debits transferred balances from sender - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let balance_before_1 = erc1155.balance_of(USER1(), 1); - let balance_before_2 = erc1155.balance_of(USER1(), 2); - let balance_before_3 = erc1155.balance_of(USER1(), 3); - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); - let balance_after_1 = erc1155.balance_of(USER1(), 1); - let balance_after_2 = erc1155.balance_of(USER1(), 2); - let balance_after_3 = erc1155.balance_of(USER1(), 3); - - assert(balance_before_1 - 1 == balance_after_1, 'invalid balance'); - assert(balance_before_2 - 10 == balance_after_2, 'invalid balance'); - assert(balance_before_3 - 20 == balance_after_3, 'invalid balance'); -} - - -#[test] -#[available_gas(50000000)] -fn test_safe_batch_transfer_from_credits_recipient() { - // credits transferred balances to receiver - let (world, erc1155) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let balance_before_1 = erc1155.balance_of(USER2(), 1); - let balance_before_2 = erc1155.balance_of(USER2(), 2); - let balance_before_3 = erc1155.balance_of(USER2(), 3); - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); - let balance_after_1 = erc1155.balance_of(USER2(), 1); - let balance_after_2 = erc1155.balance_of(USER2(), 2); - let balance_after_3 = erc1155.balance_of(USER2(), 3); - - assert(balance_before_1 + 1 == balance_after_1, 'invalid balance'); - assert(balance_before_2 + 10 == balance_after_2, 'invalid balance'); - assert(balance_before_1 + 20 == balance_after_3, 'invalid balance'); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_safe_batch_transfer_from_unapproved_operator() { - // when called by an operator on behalf of the multiTokenHolder - // when operator is not approved by multiTokenHolder - - let (world, erc1155) = deploy_testcase1(); - - // impersonate user2 - set_contract_address(USER2()); - - erc1155.safe_batch_transfer_from(USER1(), USER2(), array![1, 2], array![1, 10], array![]); -} - -#[test] -#[available_gas(50000000)] -fn test_safe_batch_transfer_from_approved_operator_preserve_operator_balance() { - // when called by an operator on behalf of the multiTokenHolder - // preserves operator's balances not involved in the transfer - - let (world, erc1155) = deploy_testcase1(); - - // impersonate proxy - set_contract_address(PROXY()); - - let balance_before_1 = erc1155.balance_of(PROXY(), 1); - let balance_before_2 = erc1155.balance_of(PROXY(), 2); - let balance_before_3 = erc1155.balance_of(PROXY(), 3); - - erc1155 - .safe_batch_transfer_from(USER1(), USER2(), array![1, 2, 3], array![1, 10, 20], array![]); - - let balance_after_1 = erc1155.balance_of(PROXY(), 1); - let balance_after_2 = erc1155.balance_of(PROXY(), 2); - let balance_after_3 = erc1155.balance_of(PROXY(), 3); - - assert(balance_before_1 == balance_after_1, 'should be equal'); - assert(balance_before_2 == balance_after_2, 'should be equal'); - assert(balance_before_3 == balance_after_3, 'should be equal'); -} -// TODO : to be continued - -// TODO : add test if we support IERC1155Receiver - - diff --git a/crates/dojo-erc/src/tests/test_erc1155_utils.cairo b/crates/dojo-erc/src/tests/test_erc1155_utils.cairo deleted file mode 100644 index 1f4f0efcae..0000000000 --- a/crates/dojo-erc/src/tests/test_erc1155_utils.cairo +++ /dev/null @@ -1,113 +0,0 @@ -use traits::{Into, TryInto}; -use option::{Option, OptionTrait}; -use result::ResultTrait; -use array::ArrayTrait; - -use starknet::ContractAddress; -use starknet::syscalls::deploy_syscall; -use starknet::testing::set_contract_address; - -use dojo::test_utils::spawn_test_world; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::erc1155::erc1155::ERC1155; -use dojo_erc::erc1155::interface::{IERC1155A, IERC1155ADispatcher, IERC1155ADispatcherTrait}; - -use dojo_erc::erc1155::components::{erc_1155_balance, uri, operator_approval}; -use dojo_erc::erc1155::systems::{ - ERC1155SetApprovalForAll, ERC1155SetUri, ERC1155SafeTransferFrom, ERC1155SafeBatchTransferFrom, - ERC1155Mint, ERC1155Burn -}; - - -fn ZERO() -> ContractAddress { - starknet::contract_address_const::<0x0>() -} - -fn DEPLOYER() -> ContractAddress { - starknet::contract_address_const::<0x420>() -} - -fn USER1() -> ContractAddress { - starknet::contract_address_const::<0x111>() -} - -fn USER2() -> ContractAddress { - starknet::contract_address_const::<0x222>() -} - -fn USER3() -> ContractAddress { - starknet::contract_address_const::<0x333>() -} - -fn PROXY() -> ContractAddress { - starknet::contract_address_const::<0x999>() -} - -fn spawn_world() -> IWorldDispatcher { - // components - let mut components = array![ - erc_1155_balance::TEST_CLASS_HASH, uri::TEST_CLASS_HASH, operator_approval::TEST_CLASS_HASH, - ]; - - // systems - let mut systems = array![ - ERC1155SetApprovalForAll::TEST_CLASS_HASH, - ERC1155SetUri::TEST_CLASS_HASH, - ERC1155SafeTransferFrom::TEST_CLASS_HASH, - ERC1155SafeBatchTransferFrom::TEST_CLASS_HASH, - ERC1155Mint::TEST_CLASS_HASH, - ERC1155Burn::TEST_CLASS_HASH, - ]; - - let world = spawn_test_world(components, systems); - world -} - -fn deploy_erc1155( - world: IWorldDispatcher, deployer: ContractAddress, uri: felt252, seed: felt252 -) -> ContractAddress { - let constructor_calldata = array![world.contract_address.into(), deployer.into(), uri]; - let (deployed_address, _) = deploy_syscall( - ERC1155::TEST_CLASS_HASH.try_into().unwrap(), seed, constructor_calldata.span(), false - ) - .expect('error deploying ERC1155'); - - deployed_address -} - - -fn deploy_default() -> (IWorldDispatcher, IERC1155ADispatcher) { - let world = spawn_world(); - let erc1155_address = deploy_erc1155(world, DEPLOYER(), 'uri', 'seed-42'); - let erc1155 = IERC1155ADispatcher { contract_address: erc1155_address }; - - (world, erc1155) -} - - -fn deploy_testcase1() -> (IWorldDispatcher, IERC1155ADispatcher) { - let world = spawn_world(); - let erc1155_address = deploy_erc1155(world, DEPLOYER(), 'uri', 'seed-42'); - let erc1155 = IERC1155ADispatcher { contract_address: erc1155_address }; - - // proxy token_id 1 x 5 - erc1155.mint(PROXY(), 1, 5, array![]); - // proxy token_id 2 x 5 - erc1155.mint(PROXY(), 2, 5, array![]); - // proxy token_id 3 x 5 - erc1155.mint(PROXY(), 3, 5, array![]); - - // user1 token_id 1 x 10 - erc1155.mint(USER1(), 1, 10, array![]); - // user1 token_id 2 x 20 - erc1155.mint(USER1(), 2, 20, array![]); - // user1 token_id 3 x 30 - erc1155.mint(USER1(), 3, 30, array![]); - - set_contract_address(USER1()); - //user1 approve_for_all proxy - erc1155.set_approval_for_all(PROXY(), true); - - (world, erc1155) -} diff --git a/crates/dojo-erc/src/tests/test_erc721.cairo b/crates/dojo-erc/src/tests/test_erc721.cairo deleted file mode 100644 index 29fda2be12..0000000000 --- a/crates/dojo-erc/src/tests/test_erc721.cairo +++ /dev/null @@ -1,878 +0,0 @@ -use core::zeroable::Zeroable; -use core::traits::{Into, Default}; -use array::ArrayTrait; -use serde::Serde; -use starknet::ContractAddress; -use starknet::testing::set_contract_address; -use option::OptionTrait; - -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::tests::test_erc721_utils::{ - spawn_world, deploy_erc721, deploy_default, deploy_testcase1, USER1, USER2, USER3, DEPLOYER, - ZERO, PROXY -}; - - -use dojo_erc::erc165::interface::IERC165_ID; -use dojo_erc::erc721::interface::{ - IERC721, IERC721ADispatcher, IERC721ADispatcherTrait, IERC721_ID, IERC721_METADATA_ID -}; -use dojo_erc::erc721::erc721::ERC721::{Event, Transfer, Approval, ApprovalForAll}; -// actually it's possible to mint -> burn -> mint -> ... -// todo : add Minted component to keep track of minted ids - -#[test] -#[available_gas(30000000)] -fn test_deploy() { - let world = spawn_world(); - let erc721_address = deploy_erc721(world, DEPLOYER(), 'name', 'symbol', 'uri', 'seed-42'); - let erc721 = IERC721ADispatcher { contract_address: erc721_address }; - - assert(erc721.owner() == DEPLOYER(), 'invalid owner'); - assert(erc721.name() == 'name', 'invalid name'); - assert(erc721.symbol() == 'symbol', 'invalid symbol'); -} - - -#[test] -#[available_gas(30000000)] -fn test_deploy_default() { - let (world, erc721) = deploy_default(); - assert(erc721.name() == 'name', 'invalid name'); -} - -// -// supports_interface -// - -#[test] -#[available_gas(30000000)] -fn test_should_support_interfaces() { - let (world, erc721) = deploy_default(); - - assert(erc721.supports_interface(IERC165_ID) == true, 'should support erc165'); - assert(erc721.supports_interface(IERC721_ID) == true, 'should support erc721'); - assert( - erc721.supports_interface(IERC721_METADATA_ID) == true, 'should support erc721_metadata' - ); -} - - -// -// behaves like an ERC721 -// - -// -// balance_of -// - -use debug::PrintTrait; - -#[test] -#[available_gas(50000000)] -fn test_balance_of_with_tokens() { - // returns the amount of tokens owned by the given address - - let (world, erc721) = deploy_testcase1(); - assert(erc721.balance_of(USER1()) == 3, 'should be 3'); - assert(erc721.balance_of(PROXY()) == 4, 'should be 4'); -} - -#[test] -#[available_gas(50000000)] -fn test_balance_of_with_no_tokens() { - // when the given address does not own any tokens - - let (world, erc721) = deploy_testcase1(); - assert(erc721.balance_of(USER3()) == 0, 'should be 0'); -} - - -#[test] -#[available_gas(50000000)] -#[should_panic] -fn test_balance_of_zero_address() { - // when querying the zero address - - let (world, erc721) = deploy_testcase1(); - erc721.balance_of(ZERO()); -} - -// -// owner_of -// - -#[test] -#[available_gas(90000000)] -fn test_owner_of_existing_id() { - // when the given token ID was tracked by this token = for existing id - - let (world, erc721) = deploy_testcase1(); - assert(erc721.owner_of(1) == USER1(), 'should be user1'); - assert(erc721.owner_of(2) == USER1(), 'should be user1'); - assert(erc721.owner_of(3) == USER1(), 'should be user1'); - - assert(erc721.owner_of(10) == PROXY(), 'should be proxy'); - assert(erc721.owner_of(11) == PROXY(), 'should be proxy'); - assert(erc721.owner_of(12) == PROXY(), 'should be proxy'); - assert(erc721.owner_of(13) == PROXY(), 'should be proxy'); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_owner_of_non_existing_id() { - // when the given token ID was not tracked by this token = non existing id - - let (world, erc721) = deploy_testcase1(); - let owner_of_0 = erc721.owner_of(0); // should panic -} - -// -// transfers -// - -#[test] -#[available_gas(90000000)] -fn test_transfer_ownership() { - // transfers the ownership of the given token ID to the given address - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let owner_of_1 = erc721.owner_of(1); - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 1); - assert(erc721.owner_of(1) == USER2(), 'invalid owner'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_event() { - // emits a Transfer event - - let (world, erc721) = deploy_default(); - - // mint - erc721.mint(USER1(), 42); - - // impersonate user1 - set_contract_address(USER1()); - - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 42); - - // impersonate user2 - set_contract_address(USER2()); - erc721.burn(42); - - // mint - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: ZERO(), to: USER1(), token_id: 42 }), - 'invalid Transfer event' - ); - // transfer - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: USER1(), to: USER2(), token_id: 42 }), - 'invalid Transfer event' - ); - // burn - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: USER2(), to: ZERO(), token_id: 42 }), - 'invalid Transfer event' - ); -} - - -#[test] -#[available_gas(90000000)] -fn test_transfer_clear_approval() { - // clears the approval for the token ID - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc721.approve(PROXY(), 1); - assert(erc721.get_approved(1) == PROXY(), 'should be proxy'); - - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 1); - assert(erc721.get_approved(1).is_zero(), 'should be zero'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_adjusts_owners_balances() { - // adjusts owners balances - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let balance_user1_before = erc721.balance_of(USER1()); - let balance_user2_before = erc721.balance_of(USER2()); - - // transfer token_id 1 to user2 - erc721.transfer(USER2(), 1); - - let balance_user1_after = erc721.balance_of(USER1()); - let balance_user2_after = erc721.balance_of(USER2()); - - assert(balance_user1_after == balance_user1_before - 1, 'invalid user1 balance'); - assert(balance_user2_after == balance_user2_before + 1, 'invalid user2 balance'); -} - - -#[test] -#[available_gas(90000000)] -fn test_transfer_from_approved() { - // when called by the approved individual - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - //user1 approve user2 for token_id 2 - erc721.approve(USER2(), 2); - - // impersonate user2 - set_contract_address(USER2()); - - erc721.transfer_from(USER1(), USER2(), 2); - assert(erc721.owner_of(2) == USER2(), 'invalid owner'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_from_approved_operator() { - // when called by the operator - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - //user1 set_approval_for_all for proxy - erc721.set_approval_for_all(PROXY(), true); - - // impersonate proxy - set_contract_address(PROXY()); - - erc721.transfer_from(USER1(), USER2(), 2); - assert(erc721.owner_of(2) == USER2(), 'invalid owner'); -} - -#[test] -#[available_gas(90000000)] -fn test_transfer_from_owner_without_approved() { - // when called by the owner without an approved user - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc721.approve(ZERO(), 2); - - erc721.transfer_from(USER1(), USER2(), 2); - assert(erc721.owner_of(2) == USER2(), 'invalid owner'); -} - - -#[test] -#[available_gas(90000000)] -fn test_transfer_to_owner() { - // when sent to the owner - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - let balance_before = erc721.balance_of(USER1()); - - assert(erc721.owner_of(3) == USER1(), 'invalid owner'); - erc721.transfer(USER1(), 3); - - // keeps ownership of the token - assert(erc721.owner_of(3) == USER1(), 'invalid owner'); - - // clears the approval for the token ID - assert(erc721.get_approved(3) == ZERO(), 'invalid approved'); - - //emits only a transfer event : cumbersome to test with pop_log - - //keeps the owner balance - let balance_after = erc721.balance_of(USER1()); - assert(balance_before == balance_after, 'invalid balance') -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_when_previous_owner_is_incorrect() { - // when the address of the previous owner is incorrect - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - //user2 owner token_id 10 - erc721.transfer_from(USER1(), PROXY(), 10); // should panic -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_when_sender_not_authorized() { - // when the sender is not authorized for the token id - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(PROXY()); - - //proxy is not authorized for USER2 - erc721.transfer_from(USER2(), PROXY(), 20); // should panic -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_when_token_id_doesnt_exists() { - // when the sender is not authorized for the token id - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(PROXY()); - - //proxy is authorized for USER1 but token_id 50 doesnt exists - erc721.transfer_from(USER1(), PROXY(), 50); // should panic -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_transfer_to_address_zero() { - // when the address to transfer the token to is the zero address - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - - erc721.transfer(ZERO(), 1); // should panic -} - -// -// approval -// - -// when clearing approval - -#[test] -#[available_gas(90000000)] -fn test_approval_when_clearing_with_prior_approval() { - // -when there was a prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - // impersonate user1 - set_contract_address(USER1()); - erc721.approve(PROXY(), 42); - - //revoke approve - erc721.approve(ZERO(), 42); - - // clears approval for the token - assert(erc721.get_approved(42) == ZERO(), 'invalid approved'); - - // emits an approval event - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop approve PROXY - - // approve ZERO - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: ZERO(), token_id: 42 }), - 'invalid Approval event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_when_clearing_without_prior_approval() { - // when clearing approval - // -when there was no prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - // impersonate user1 - set_contract_address(USER1()); - - //revoke approve - erc721.approve(ZERO(), 42); - - // updates approval for the token - assert(erc721.get_approved(42) == ZERO(), 'invalid approved'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - - // approve ZERO - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: ZERO(), token_id: 42 }), - 'invalid Approval event' - ); -} - - -// when approving a non-zero address - -#[test] -#[available_gas(90000000)] -fn test_approval_non_zero_address_with_prior_approval() { - // -when there was a prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - // impersonate user1 - set_contract_address(USER1()); - erc721.approve(PROXY(), 42); - - // user1 approves user3 - erc721.approve(USER3(), 42); - - // set approval for the token - assert(erc721.get_approved(42) == USER3(), 'invalid approved'); - - // emits an approval event - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop approve PROXY - - // approve USER3 - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER3(), token_id: 42 }), - 'invalid Approval event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_non_zero_address_with_no_prior_approval() { - // -when there was no prior approval - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - - // impersonate user1 - set_contract_address(USER1()); - - // user1 approves user3 - erc721.approve(USER3(), 42); - - // set approval for the token - assert(erc721.get_approved(42) == USER3(), 'invalid approved'); - - // emits an approval event - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - - // approve USER3 - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER3(), token_id: 42 }), - 'invalid Approval event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_self_approve() { - // when the address that receives the approval is the owner - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 42); - // impersonate user1 - set_contract_address(USER1()); - // user1 approves user1 - erc721.approve(USER1(), 42); // should panic -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_not_owned() { - // when the sender does not own the given token ID - - let (world, erc721) = deploy_testcase1(); - // impersonate user1 - set_contract_address(USER1()); - // user1 approves user2 for token 20 - erc721.approve(USER2(), 20); // should panic -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_from_approved_sender() { - // when the sender is approved for the given token ID - - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - // user1 approve user3 - erc721.approve(USER3(), 1); - - // impersonate USER3 - set_contract_address(USER3()); - // (ERC721: approve caller is not token owner or approved for all) - erc721.approve(USER2(), 1); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_approval_from_approved_operator() { - // when the sender is an operator - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 50); - - // impersonate user1 - set_contract_address(USER1()); - erc721.set_approval_for_all(PROXY(), true); - - // impersonate proxy , proxy is operator for user1 - set_contract_address(PROXY()); - // proxy approves user2 for token 20 - erc721.approve(USER2(), 50); - - assert(erc721.get_approved(50) == USER2(), 'invalid approval'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address).unwrap(); // unpop mint - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all - - // approve - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Approval(Approval { owner: USER1(), to: USER2(), token_id: 50 }), - 'invalid Approval event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_unexisting_id() { - // when the given token ID does not exist - let (world, erc721) = deploy_testcase1(); - - // impersonate user1 - set_contract_address(USER1()); - // user1 approve user3 - erc721.approve(USER3(), 69); // should panic -} - -// -// approval_for_all -// - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_is_not_owner_no_operator_approval() { - // when the operator willing to approve is not the owner - // -when there is no operator approval set by the sender - let (world, erc721) = deploy_default(); - - // impersonate user2 - set_contract_address(USER2()); - - // user2 set_approval_for_all PROXY - erc721.set_approval_for_all(PROXY(), true); - - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } - ), - 'invalid ApprovalForAll event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_is_not_owner_from_not_approved() { - // when the operator willing to approve is not the owner - // -when the operator was set as not approved - let (world, erc721) = deploy_default(); - - // impersonate user2 - set_contract_address(USER2()); - - erc721.set_approval_for_all(PROXY(), false); - - // user2 set_approval_for_all PROXY - erc721.set_approval_for_all(PROXY(), true); - - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), false) - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } - ), - 'invalid ApprovalForAll event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_is_not_owner_can_unset_approval_for_all() { - // when the operator willing to approve is not the owner - // can unset the operator approval - let (world, erc721) = deploy_default(); - - // impersonate user2 - set_contract_address(USER2()); - - erc721.set_approval_for_all(PROXY(), false); - erc721.set_approval_for_all(PROXY(), true); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - erc721.set_approval_for_all(PROXY(), false); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == false, 'invalid is_approved_for_all'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), false) - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), true) - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: false } - ), - 'invalid ApprovalForAll event' - ); -} - -#[test] -#[available_gas(90000000)] -fn test_approval_for_all_operator_with_operator_already_approved() { - // when the operator willing to approve is not the owner - // when the operator was already approved - let (world, erc721) = deploy_default(); - - // impersonate user2 - set_contract_address(USER2()); - - erc721.set_approval_for_all(PROXY(), true); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - erc721.set_approval_for_all(PROXY(), true); - assert(erc721.is_approved_for_all(USER2(), PROXY()) == true, 'invalid is_approved_for_all'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop set_approval_for_all(PROXY(), true) - - // ApproveForAll - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::ApprovalForAll( - ApprovalForAll { owner: USER2(), operator: PROXY(), approved: true } - ), - 'invalid ApprovalForAll event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_approval_for_all_with_owner_as_operator() { - // when the operator is the owner - - let (world, erc721) = deploy_default(); - - // impersonate user1 - set_contract_address(USER1()); - erc721.set_approval_for_all(USER1(), true); // should panic -} - - -// -// get_approved -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_get_approved_unexisting_token() { - let (world, erc721) = deploy_default(); - - erc721.get_approved(420); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_get_approved_with_existing_token() { - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 420); - assert(erc721.get_approved(420) == ZERO(), 'invalid get_approved'); -} - - -#[test] -#[available_gas(90000000)] -fn test_get_approved_with_existing_token_and_approval() { - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 420); - - // impersonate user1 - set_contract_address(USER1()); - erc721.approve(PROXY(), 420); - assert(erc721.get_approved(420) == PROXY(), 'invalid get_approved'); -} - -// -// mint -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_mint_to_address_zero() { - // reverts with a null destination address - - let (world, erc721) = deploy_default(); - - erc721.mint(ZERO(), 69); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_mint() { - // reverts with a null destination address - - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 69); - - assert(erc721.balance_of(USER1()) == 1, 'invalid balance'); - - // Transfer - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: ZERO(), to: USER1(), token_id: 69 }), - 'invalid Transfer event' - ); -} - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_mint_existing_token_id() { - // reverts with a null destination address - - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 69); - erc721.mint(USER1(), 69); //should panic -} - - -// -// burn -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_burn_non_existing_token_id() { - //reverts when burning a non-existent token id - let (world, erc721) = deploy_default(); - erc721.burn(69); // should panic -} - - -#[test] -#[available_gas(90000000)] -fn test_burn() { - //reverts when burning a non-existent token id - let (world, erc721) = deploy_default(); - - erc721.mint(USER1(), 69); - assert(erc721.balance_of(USER1()) == 1, 'invalid balance'); - erc721.burn(69); - assert(erc721.balance_of(USER1()) == 0, 'invalid balance'); - - let _: Event = starknet::testing::pop_log(erc721.contract_address) - .unwrap(); // unpop erc721.mint(USER1(), 69) - - // Transfer - assert( - @starknet::testing::pop_log(erc721.contract_address) - .unwrap() == @Event::Transfer(Transfer { from: USER1(), to: ZERO(), token_id: 69 }), - 'invalid Transfer event' - ); -} - - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_burn_same_id_twice() { - // reverts when burning a token id that has been deleted - let (world, erc721) = deploy_default(); - erc721.mint(USER1(), 69); - erc721.burn(69); - erc721.burn(69); // should panic -} - -// -// token_uri -// - -#[test] -#[available_gas(90000000)] -#[should_panic] -fn test_token_uri_for_non_existing_token_id() { - // reverts when queried for non existent token id - let (world, erc721) = deploy_default(); - erc721.token_uri(1234); // should panic -} - diff --git a/crates/dojo-erc/src/tests/test_erc721_utils.cairo b/crates/dojo-erc/src/tests/test_erc721_utils.cairo deleted file mode 100644 index 1ede104955..0000000000 --- a/crates/dojo-erc/src/tests/test_erc721_utils.cairo +++ /dev/null @@ -1,127 +0,0 @@ -use traits::{Into, TryInto}; -use option::{Option, OptionTrait}; -use result::ResultTrait; -use array::ArrayTrait; - -use starknet::ContractAddress; -use starknet::syscalls::deploy_syscall; -use starknet::testing::set_contract_address; - -use dojo::test_utils::spawn_test_world; -use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; - -use dojo_erc::erc721::erc721::ERC721; -use dojo_erc::erc721::interface::{IERC721, IERC721ADispatcher, IERC721ADispatcherTrait}; - -use dojo_erc::erc721::components::{ - erc_721_balance, erc_721_owner, erc_721_token_approval, operator_approval, base_uri -}; -use dojo_erc::erc721::systems::{ - ERC721Approve, ERC721SetApprovalForAll, ERC721TransferFrom, ERC721Mint, ERC721Burn, - ERC721SetBaseUri -}; - -fn DEPLOYER() -> ContractAddress { - starknet::contract_address_const::<0x420>() -} - -fn USER1() -> ContractAddress { - starknet::contract_address_const::<0x111>() -} - -fn USER2() -> ContractAddress { - starknet::contract_address_const::<0x222>() -} - -fn USER3() -> ContractAddress { - starknet::contract_address_const::<0x333>() -} - -fn ZERO() -> ContractAddress { - starknet::contract_address_const::<0x0>() -} - -fn PROXY() -> ContractAddress { - starknet::contract_address_const::<0x999>() -} - -fn spawn_world() -> IWorldDispatcher { - // components - let mut components = array![ - erc_721_balance::TEST_CLASS_HASH, - erc_721_owner::TEST_CLASS_HASH, - erc_721_token_approval::TEST_CLASS_HASH, - operator_approval::TEST_CLASS_HASH, - base_uri::TEST_CLASS_HASH, - ]; - - // systems - let mut systems = array![ - ERC721Approve::TEST_CLASS_HASH, - ERC721SetApprovalForAll::TEST_CLASS_HASH, - ERC721TransferFrom::TEST_CLASS_HASH, - ERC721Mint::TEST_CLASS_HASH, - ERC721Burn::TEST_CLASS_HASH, - ERC721SetBaseUri::TEST_CLASS_HASH, - ]; - - let world = spawn_test_world(components, systems); - world -} - - -fn deploy_erc721( - world: IWorldDispatcher, - deployer: ContractAddress, - name: felt252, - symbol: felt252, - uri: felt252, - seed: felt252 -) -> ContractAddress { - let constructor_calldata = array![ - world.contract_address.into(), deployer.into(), name, symbol, uri - ]; - let (deployed_address, _) = deploy_syscall( - ERC721::TEST_CLASS_HASH.try_into().unwrap(), seed, constructor_calldata.span(), false - ) - .expect('error deploying ERC721'); - - deployed_address -} - - -fn deploy_default() -> (IWorldDispatcher, IERC721ADispatcher) { - let world = spawn_world(); - let erc721_address = deploy_erc721(world, DEPLOYER(), 'name', 'symbol', 'uri', 'seed-42'); - let erc721 = IERC721ADispatcher { contract_address: erc721_address }; - - (world, erc721) -} - - -fn deploy_testcase1() -> (IWorldDispatcher, IERC721ADispatcher) { - let world = spawn_world(); - let erc721_address = deploy_erc721(world, DEPLOYER(), 'name', 'symbol', 'uri', 'seed-42'); - let erc721 = IERC721ADispatcher { contract_address: erc721_address }; - - // user1 owns id : 1,2,3 - erc721.mint(USER1(), 1); - erc721.mint(USER1(), 2); - erc721.mint(USER1(), 3); - - // proxy owns id : 10, 11,12,13 - erc721.mint(PROXY(), 10); - erc721.mint(PROXY(), 11); - erc721.mint(PROXY(), 12); - erc721.mint(PROXY(), 13); - - //user2 owns id : 20 - erc721.mint(USER2(), 20); - - set_contract_address(USER1()); - //user1 approve_for_all proxy - erc721.set_approval_for_all(PROXY(), true); - - (world, erc721) -} - diff --git a/crates/dojo-erc/src/tests/utils.cairo b/crates/dojo-erc/src/tests/utils.cairo new file mode 100644 index 0000000000..c42ce68c97 --- /dev/null +++ b/crates/dojo-erc/src/tests/utils.cairo @@ -0,0 +1,38 @@ +// mod dojo_erc::tests::constants; + +use array::ArrayTrait; +use array::SpanTrait; +use core::result::ResultTrait; +use option::OptionTrait; +use starknet::class_hash::Felt252TryIntoClassHash; +use starknet::ContractAddress; +use starknet::testing; +use traits::TryInto; + +fn deploy(contract_class_hash: felt252, calldata: Array) -> ContractAddress { + let (address, _) = starknet::deploy_syscall( + contract_class_hash.try_into().unwrap(), 0, calldata.span(), false + ) + .unwrap(); + address +} + +/// Pop the earliest unpopped logged event for the contract as the requested type +/// and checks there's no more data left on the event, preventing unaccounted params. +/// Indexed event members are currently not supported, so they are ignored. +fn pop_log, impl TEvent: starknet::Event>( + address: ContractAddress +) -> Option { + let (mut keys, mut data) = testing::pop_log_raw(address)?; + let ret = starknet::Event::deserialize(ref keys, ref data); + assert(data.is_empty(), 'Event has extra data'); + ret +} + +fn assert_no_events_left(address: ContractAddress) { + assert(testing::pop_log_raw(address).is_none(), 'Events remaining on queue'); +} + +fn drop_event(address: ContractAddress) { + testing::pop_log_raw(address); +} diff --git a/crates/dojo-erc/src/token/erc20.cairo b/crates/dojo-erc/src/token/erc20.cairo new file mode 100644 index 0000000000..238e05a1e6 --- /dev/null +++ b/crates/dojo-erc/src/token/erc20.cairo @@ -0,0 +1,331 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +trait IERC20CamelOnly { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} + +#[starknet::contract] +mod ERC20 { + use dojo_erc::token::erc20_models::{ERC20Allowance, ERC20Balance, ERC20Meta}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use integer::BoundedInt; + use super::IERC20; + use super::IERC20CamelOnly; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use zeroable::Zeroable; + use debug::PrintTrait; + + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Copy, Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + } + + #[derive(Copy, Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256 + } + + mod Errors { + const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; + const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; + const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; + const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; + const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; + const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress + ) { + self._world.write(world); + self.initializer(name, symbol); + self._mint(recipient, initial_supply); + } + + // + // External + // + + #[external(v0)] + impl ERC20Impl of IERC20 { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn decimals(self: @ContractState) -> u8 { + 18 + } + + fn total_supply(self: @ContractState) -> u256 { + self.get_meta().total_supply + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self.get_balance(account).amount + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.get_allowance(owner, spender).amount + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + self._transfer(sender, recipient, amount); + true + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + let caller = get_caller_address(); + self._spend_allowance(sender, caller, amount); + self._transfer(sender, recipient, amount); + true + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let owner = get_caller_address(); + self + .set_allowance( + ERC20Allowance { token: get_contract_address(), owner, spender, amount } + ); + true + } + } + + #[external(v0)] + impl ERC20CamelOnlyImpl of IERC20CamelOnly { + fn totalSupply(self: @ContractState) -> u256 { + ERC20Impl::total_supply(self) + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC20Impl::balance_of(self, account) + } + + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + ERC20Impl::transfer_from(ref self, sender, recipient, amount) + } + } + + #[external(v0)] + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) -> bool { + self.update_allowance(get_caller_address(), spender, 0, added_value); + true + } + + #[external(v0)] + fn increaseAllowance( + ref self: ContractState, spender: ContractAddress, addedValue: u256 + ) -> bool { + increase_allowance(ref self, spender, addedValue) + } + + #[external(v0)] + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) -> bool { + self.update_allowance(get_caller_address(), spender, subtracted_value, 0); + true + } + + #[external(v0)] + fn decreaseAllowance( + ref self: ContractState, spender: ContractAddress, subtractedValue: u256 + ) -> bool { + decrease_allowance(ref self, spender, subtractedValue) + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC20Meta { + get!(self.world(), get_contract_address(), ERC20Meta) + } + + // Helper function to update total_supply model + fn update_total_supply(ref self: ContractState, subtract: u256, add: u256) { + let mut meta = self.get_meta(); + // adding and subtracting is fewer steps than if + meta.total_supply = meta.total_supply - subtract; + meta.total_supply = meta.total_supply + add; + set!(self.world(), (meta)); + } + + // Helper function for balance model + fn get_balance(self: @ContractState, account: ContractAddress) -> ERC20Balance { + get!(self.world(), (get_contract_address(), account), ERC20Balance) + } + + fn update_balance( + ref self: ContractState, account: ContractAddress, subtract: u256, add: u256 + ) { + let mut balance: ERC20Balance = self.get_balance(account); + // adding and subtracting is fewer steps than if + balance.amount = balance.amount - subtract; + balance.amount = balance.amount + add; + set!(self.world(), (balance)); + } + + // Helper function for allowance model + fn get_allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress, + ) -> ERC20Allowance { + get!(self.world(), (get_contract_address(), owner, spender), ERC20Allowance) + } + + fn update_allowance( + ref self: ContractState, + owner: ContractAddress, + spender: ContractAddress, + subtract: u256, + add: u256 + ) { + let mut allowance = self.get_allowance(owner, spender); + // adding and subtracting is fewer steps than if + allowance.amount = allowance.amount - subtract; + allowance.amount = allowance.amount + add; + self.set_allowance(allowance); + } + + fn set_allowance(ref self: ContractState, allowance: ERC20Allowance) { + assert(!allowance.owner.is_zero(), Errors::APPROVE_FROM_ZERO); + assert(!allowance.spender.is_zero(), Errors::APPROVE_TO_ZERO); + set!(self.world(), (allowance)); + self + .emit_event( + Approval { + owner: allowance.owner, spender: allowance.spender, value: allowance.amount + } + ); + } + + fn emit_event< + S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SCopy: Copy + >( + ref self: ContractState, event: S + ) { + self.emit(event); + emit!(self.world(), event); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252) { + let meta = ERC20Meta { token: get_contract_address(), name, symbol, total_supply: 0 }; + set!(self.world(), (meta)); + } + + fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); + self.update_total_supply(0, amount); + self.update_balance(recipient, 0, amount); + self.emit_event(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); + } + + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { + assert(!account.is_zero(), Errors::BURN_FROM_ZERO); + self.update_total_supply(amount, 0); + self.update_balance(account, amount, 0); + self.emit_event(Transfer { from: account, to: Zeroable::zero(), value: amount }); + } + + fn _approve( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + self + .set_allowance( + ERC20Allowance { token: get_contract_address(), owner, spender, amount } + ); + } + + fn _transfer( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); + assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); + self.update_balance(sender, amount, 0); + self.update_balance(recipient, 0, amount); + self.emit_event(Transfer { from: sender, to: recipient, value: amount }); + } + + fn _spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + let current_allowance = self.get_allowance(owner, spender).amount; + if current_allowance != BoundedInt::max() { + self.update_allowance(owner, spender, amount, 0); + } + } + } +} diff --git a/crates/dojo-erc/src/token/erc20_models.cairo b/crates/dojo-erc/src/token/erc20_models.cairo new file mode 100644 index 0000000000..ab68718dc9 --- /dev/null +++ b/crates/dojo-erc/src/token/erc20_models.cairo @@ -0,0 +1,30 @@ +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + amount: u256, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20Allowance { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + amount: u256, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + total_supply: u256, +} diff --git a/crates/dojo-erc/src/token/erc721.cairo b/crates/dojo-erc/src/token/erc721.cairo new file mode 100644 index 0000000000..15b96462ac --- /dev/null +++ b/crates/dojo-erc/src/token/erc721.cairo @@ -0,0 +1,5 @@ +mod erc721; +mod models; +mod interface; + +use erc721::ERC721; diff --git a/crates/dojo-erc/src/token/erc721/erc721.cairo b/crates/dojo-erc/src/token/erc721/erc721.cairo new file mode 100644 index 0000000000..ffd7915d82 --- /dev/null +++ b/crates/dojo-erc/src/token/erc721/erc721.cairo @@ -0,0 +1,443 @@ +#[starknet::contract] +mod ERC721 { + use dojo_erc::token::erc721::models::{ + ERC721Meta, ERC721OperatorApproval, ERC721Owner, ERC721Balance, ERC721TokenApproval + }; + use dojo_erc::token::erc721::interface; + use dojo_erc::token::erc721::interface::{IERC721, IERC721CamelOnly}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use integer::BoundedInt; + use starknet::ContractAddress; + use starknet::{get_caller_address, get_contract_address}; + use zeroable::Zeroable; + use debug::PrintTrait; + + + #[storage] + struct Storage { + _world: ContractAddress, + } + + #[event] + #[derive(Copy, Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + ApprovalForAll: ApprovalForAll + } + + #[derive(Copy, Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + token_id: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + approved: ContractAddress, + token_id: u256 + } + + #[derive(Copy, Drop, starknet::Event)] + struct ApprovalForAll { + owner: ContractAddress, + operator: ContractAddress, + approved: bool + } + + mod Errors { + const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; + const INVALID_ACCOUNT: felt252 = 'ERC721: invalid account'; + const UNAUTHORIZED: felt252 = 'ERC721: unauthorized caller'; + const APPROVAL_TO_OWNER: felt252 = 'ERC721: approval to owner'; + const SELF_APPROVAL: felt252 = 'ERC721: self approval'; + const INVALID_RECEIVER: felt252 = 'ERC721: invalid receiver'; + const ALREADY_MINTED: felt252 = 'ERC721: token already minted'; + const WRONG_SENDER: felt252 = 'ERC721: wrong sender'; + const SAFE_MINT_FAILED: felt252 = 'ERC721: safe mint failed'; + const SAFE_TRANSFER_FAILED: felt252 = 'ERC721: safe transfer failed'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + world: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, + recipient: ContractAddress, + token_id: u256 + ) { + self._world.write(world); + self.initializer(name, symbol, base_uri); + self._mint(recipient, token_id); + } + + // + // External + // + + // #[external(v0)] + // impl SRC5Impl of ISRC5 { + // fn supports_interface(self: @ContractState, interface_id: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id) + // } + // } + + // #[external(v0)] + // impl SRC5CamelImpl of ISRC5Camel { + // fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool { + // let unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId) + // } + // } + + #[external(v0)] + impl ERC721MetadataImpl of interface::IERC721Metadata { + fn name(self: @ContractState) -> felt252 { + self.get_meta().name + } + + fn symbol(self: @ContractState) -> felt252 { + self.get_meta().symbol + } + + fn token_uri(self: @ContractState, token_id: u256) -> felt252 { + assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + // TODO : concat with id + self.get_uri(token_id) + } + } + + #[external(v0)] + impl ERC721MetadataCamelOnlyImpl of interface::IERC721MetadataCamelOnly { + fn tokenURI(self: @ContractState, tokenId: u256) -> felt252 { + assert(self._exists(tokenId), Errors::INVALID_TOKEN_ID); + self.get_uri(tokenId) + } + } + + #[external(v0)] + impl ERC721Impl of interface::IERC721 { + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + assert(account.is_non_zero(), Errors::INVALID_ACCOUNT); + self.get_balance(account).amount + } + + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + self._owner_of(token_id) + } + + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { + assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + self.get_token_approval(token_id).address + } + + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + self.get_operator_approval(owner, operator).approved + } + + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self._owner_of(token_id); + + let caller = get_caller_address(); + assert( + owner == caller || ERC721Impl::is_approved_for_all(@self, owner, caller), + Errors::UNAUTHORIZED + ); + self._approve(to, token_id); + } + + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { + self._set_approval_for_all(get_caller_address(), operator, approved) + } + + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED + ); + self._transfer(from, to, token_id); + } + + fn safe_transfer_from( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED + ); + self._safe_transfer(from, to, token_id, data); + } + } + + #[external(v0)] + impl ERC721CamelOnlyImpl of interface::IERC721CamelOnly { + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC721Impl::balance_of(self, account) + } + + fn ownerOf(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721Impl::owner_of(self, tokenId) + } + + fn getApproved(self: @ContractState, tokenId: u256) -> ContractAddress { + ERC721Impl::get_approved(self, tokenId) + } + + fn isApprovedForAll( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + ERC721Impl::is_approved_for_all(self, owner, operator) + } + + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) { + ERC721Impl::set_approval_for_all(ref self, operator, approved) + } + + fn transferFrom( + ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256 + ) { + ERC721Impl::transfer_from(ref self, from, to, tokenId) + } + + fn safeTransferFrom( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ) { + ERC721Impl::safe_transfer_from(ref self, from, to, tokenId, data) + } + } + + // + // Internal + // + + #[generate_trait] + impl WorldInteractionsImpl of WorldInteractionsTrait { + fn world(self: @ContractState) -> IWorldDispatcher { + IWorldDispatcher { contract_address: self._world.read() } + } + + fn get_meta(self: @ContractState) -> ERC721Meta { + get!(self.world(), get_contract_address(), ERC721Meta) + } + + fn get_uri(self: @ContractState, token_id: u256) -> felt252 { + // TODO : concat with id when we have string type + self.get_meta().base_uri + } + + fn get_balance(self: @ContractState, account: ContractAddress) -> ERC721Balance { + get!(self.world(), (get_contract_address(), account), ERC721Balance) + } + + fn get_owner_of(self: @ContractState, token_id: u256) -> ERC721Owner { + get!(self.world(), (get_contract_address(), token_id), ERC721Owner) + } + + fn get_token_approval(self: @ContractState, token_id: u256) -> ERC721TokenApproval { + get!(self.world(), (get_contract_address(), token_id), ERC721TokenApproval) + } + + fn get_operator_approval( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> ERC721OperatorApproval { + get!(self.world(), (get_contract_address(), owner, operator), ERC721OperatorApproval) + } + + fn set_token_approval( + ref self: ContractState, + owner: ContractAddress, + to: ContractAddress, + token_id: u256, + emit: bool + ) { + set!( + self.world(), + ERC721TokenApproval { token: get_contract_address(), token_id, address: to, } + ); + if emit { + self.emit_event(Approval { owner, approved: to, token_id }); + } + } + + fn set_operator_approval( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + set!( + self.world(), + ERC721OperatorApproval { token: get_contract_address(), owner, operator, approved } + ); + self.emit_event(ApprovalForAll { owner, operator, approved }); + } + + fn set_balance(ref self: ContractState, account: ContractAddress, amount: u256) { + set!(self.world(), ERC721Balance { token: get_contract_address(), account, amount }); + } + + fn set_owner(ref self: ContractState, token_id: u256, address: ContractAddress) { + set!(self.world(), ERC721Owner { token: get_contract_address(), token_id, address }); + } + + fn emit_event< + S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SCopy: Copy + >( + ref self: ContractState, event: S + ) { + self.emit(event); + emit!(self.world(), event); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252, base_uri: felt252) { + let meta = ERC721Meta { token: get_contract_address(), name, symbol, base_uri }; + set!(self.world(), (meta)); + // let mut unsafe_state = src5::SRC5::unsafe_new_contract_state(); + // src5::SRC5::InternalImpl::register_interface(ref unsafe_state, interface::IERC721_ID); + // src5::SRC5::InternalImpl::register_interface( + // ref unsafe_state, interface::IERC721_METADATA_ID + // ); + } + + fn _owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + let owner = self.get_owner_of(token_id).address; + match owner.is_zero() { + bool::False(()) => owner, + bool::True(()) => panic_with_felt252(Errors::INVALID_TOKEN_ID) + } + } + + fn _exists(self: @ContractState, token_id: u256) -> bool { + let owner = self.get_owner_of(token_id).address; + owner.is_non_zero() + } + + fn _is_approved_or_owner( + self: @ContractState, spender: ContractAddress, token_id: u256 + ) -> bool { + let owner = self._owner_of(token_id); + let is_approved_for_all = ERC721Impl::is_approved_for_all(self, owner, spender); + owner == spender + || is_approved_for_all + || spender == ERC721Impl::get_approved(self, token_id) + } + + fn _approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self._owner_of(token_id); + assert(owner != to, Errors::APPROVAL_TO_OWNER); + + self.set_token_approval(owner, to, token_id, true); + } + + fn _set_approval_for_all( + ref self: ContractState, + owner: ContractAddress, + operator: ContractAddress, + approved: bool + ) { + assert(owner != operator, Errors::SELF_APPROVAL); + self.set_operator_approval(owner, operator, approved); + } + + fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + assert(!to.is_zero(), Errors::INVALID_RECEIVER); + assert(!self._exists(token_id), Errors::ALREADY_MINTED); + + self.set_balance(to, self.get_balance(to).amount + 1); + self.set_owner(token_id, to); + + self.emit_event(Transfer { from: Zeroable::zero(), to, token_id }); + } + + fn _transfer( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert(!to.is_zero(), Errors::INVALID_RECEIVER); + let owner = self._owner_of(token_id); + assert(from == owner, Errors::WRONG_SENDER); + + // Implicit clear approvals, no need to emit an event + self.set_token_approval(owner, Zeroable::zero(), token_id, false); + + self.set_balance(from, self.get_balance(from).amount - 1); + self.set_balance(to, self.get_balance(to).amount + 1); + self.set_owner(token_id, to); + + self.emit_event(Transfer { from, to, token_id }); + } + + fn _burn(ref self: ContractState, token_id: u256) { + let owner = self._owner_of(token_id); + + // Implicit clear approvals, no need to emit an event + self.set_token_approval(owner, Zeroable::zero(), token_id, false); + + self.set_balance(owner, self.get_balance(owner).amount - 1); + self.set_owner(token_id, Zeroable::zero()); + + self.emit_event(Transfer { from: owner, to: Zeroable::zero(), token_id }); + } + + fn _safe_mint( + ref self: ContractState, to: ContractAddress, token_id: u256, data: Span + ) { + self._mint(to, token_id); + // assert( + // _check_on_erc721_received(Zeroable::zero(), to, token_id, data), + // Errors::SAFE_MINT_FAILED + // ); + } + + fn _safe_transfer( + ref self: ContractState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ) { + self._transfer(from, to, token_id); + // assert( + // _check_on_erc721_received(from, to, token_id, data), Errors::SAFE_TRANSFER_FAILED + // ); + } + // fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { + // assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + // self._token_uri.write(token_id, token_uri) + // } + } + +//#[internal] +// fn _check_on_erc721_received( +// from: ContractAddress, to: ContractAddress, token_id: u256, data: Span +// ) -> bool { +// if (DualCaseSRC5 { contract_address: to } +// .supports_interface(interface::IERC721_RECEIVER_ID)) { +// DualCaseERC721Receiver { contract_address: to } +// .on_erc721_received( +// get_caller_address(), from, token_id, data +// ) == interface::IERC721_RECEIVER_ID +// } else { +// DualCaseSRC5 { contract_address: to }.supports_interface(account::interface::ISRC6_ID) +// } +// } +} diff --git a/crates/dojo-erc/src/token/erc721/interface.cairo b/crates/dojo-erc/src/token/erc721/interface.cairo new file mode 100644 index 0000000000..730ad2e027 --- /dev/null +++ b/crates/dojo-erc/src/token/erc721/interface.cairo @@ -0,0 +1,60 @@ +use starknet::ContractAddress; + +const IERC721_ID: felt252 = 0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943; +const IERC721_METADATA_ID: felt252 = + 0x6069a70848f907fa57668ba1875164eb4dcee693952468581406d131081bbd; +const IERC721_RECEIVER_ID: felt252 = + 0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc; + +#[starknet::interface] +trait IERC721 { + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn owner_of(self: @TState, token_id: u256) -> ContractAddress; + fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn approve(ref self: TState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + fn get_approved(self: @TState, token_id: u256) -> ContractAddress; + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; +} + +#[starknet::interface] +trait IERC721CamelOnly { + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn ownerOf(self: @TState, tokenId: u256) -> ContractAddress; + fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256); + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; +} + +// +// IERC721Metadata +// + +#[starknet::interface] +trait IERC721Metadata { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn token_uri(self: @TState, token_id: u256) -> felt252; +} + +#[starknet::interface] +trait IERC721MetadataCamelOnly { + fn tokenURI(self: @TState, tokenId: u256) -> felt252; +} diff --git a/crates/dojo-erc/src/token/erc721/models.cairo b/crates/dojo-erc/src/token/erc721/models.cairo new file mode 100644 index 0000000000..1bc4bcadd6 --- /dev/null +++ b/crates/dojo-erc/src/token/erc721/models.cairo @@ -0,0 +1,48 @@ +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721Meta { + #[key] + token: ContractAddress, + name: felt252, + symbol: felt252, + base_uri: felt252, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721OperatorApproval { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + operator: ContractAddress, + approved: bool +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721Owner { + #[key] + token: ContractAddress, + #[key] + token_id: u256, + address: ContractAddress +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721Balance { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + amount: u256, +} + +#[derive(Model, Copy, Drop, Serde)] +struct ERC721TokenApproval { + #[key] + token: ContractAddress, + #[key] + token_id: u256, + address: ContractAddress, +} \ No newline at end of file diff --git a/crates/dojo-lang/Cargo.toml b/crates/dojo-lang/Cargo.toml index 796fe360f6..0a2f0ebeee 100644 --- a/crates/dojo-lang/Cargo.toml +++ b/crates/dojo-lang/Cargo.toml @@ -13,6 +13,7 @@ testing = [ ] anyhow.workspace = true assert_fs = "1.0.9" cairo-lang-compiler.workspace = true +cairo-lang-debug.workspace = true cairo-lang-defs.workspace = true cairo-lang-diagnostics.workspace = true cairo-lang-filesystem.workspace = true @@ -31,6 +32,7 @@ dojo-types = { path = "../dojo-types" } dojo-world = { path = "../dojo-world" } indoc.workspace = true itertools.workspace = true +once_cell.workspace = true salsa.workspace = true sanitizer = "0.1.6" scarb.workspace = true diff --git a/crates/dojo-lang/README.md b/crates/dojo-lang/README.md index 961d8e77bf..2868ce5cb4 100644 --- a/crates/dojo-lang/README.md +++ b/crates/dojo-lang/README.md @@ -4,12 +4,12 @@ Cairo language plugin for compiling the Dojo Entity Component System to Starknet ## Testing -Expected test outputs are defined in `crates/dojo-lang/src/plugin_test_data/component`. +Expected test outputs are defined in `crates/dojo-lang/src/plugin_test_data/model`. To run the tests, run: ``` -cargo test --package dojo-lang --lib -- plugin::test::expand_contract::component --exact --nocapture +cargo test --package dojo-lang --lib -- plugin::test::expand_contract::model --exact --nocapture ``` To regenerate, set `CAIRO_FIX_TESTS=1`. diff --git a/crates/dojo-lang/src/compiler.rs b/crates/dojo-lang/src/compiler.rs index 20c36e6a1a..d41583355c 100644 --- a/crates/dojo-lang/src/compiler.rs +++ b/crates/dojo-lang/src/compiler.rs @@ -138,7 +138,9 @@ fn find_project_contracts( .iter() .map(|selector| selector.package().into()) .unique() - .map(|package_name: SmolStr| db.upcast_mut().intern_crate(CrateLongId(package_name))) + .map(|package_name: SmolStr| { + db.upcast_mut().intern_crate(CrateLongId::Real(package_name)) + }) .collect::>(); find_contracts(db, crate_ids.as_ref()) .into_iter() @@ -161,13 +163,11 @@ pub fn collect_core_crate_ids(db: &RootDatabase) -> Vec { [ ContractSelector("dojo::executor::executor".to_string()), ContractSelector("dojo::world::world".to_string()), - ContractSelector("dojo::world::library_call".to_string()), - ContractSelector("dojo::world_factory::world_factory".to_string()), ] .iter() .map(|selector| selector.package().into()) .unique() - .map(|package_name: SmolStr| db.intern_crate(CrateLongId(package_name))) + .map(|package_name: SmolStr| db.intern_crate(CrateLongId::Real(package_name))) .collect::>() } @@ -179,7 +179,7 @@ pub fn collect_external_crate_ids( .iter() .map(|selector| selector.package().into()) .unique() - .map(|package_name: SmolStr| db.intern_crate(CrateLongId(package_name))) + .map(|package_name: SmolStr| db.intern_crate(CrateLongId::Real(package_name))) .collect::>() } diff --git a/crates/dojo-lang/src/component.rs b/crates/dojo-lang/src/component.rs deleted file mode 100644 index c13c5620cb..0000000000 --- a/crates/dojo-lang/src/component.rs +++ /dev/null @@ -1,247 +0,0 @@ -use cairo_lang_defs::plugin::PluginDiagnostic; -use cairo_lang_semantic::patcher::RewriteNode; -use cairo_lang_syntax::node::ast::ItemStruct; -use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::helpers::QueryAttrs; -use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode}; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; -use convert_case::{Case, Casing}; -use dojo_types::component::Member; -use itertools::Itertools; - -use crate::plugin::{Component, DojoAuxData}; - -/// A handler for Dojo code that modifies a component struct. -/// Parameters: -/// * db: The semantic database. -/// * struct_ast: The AST of the component struct. -/// Returns: -/// * A RewriteNode containing the generated code. -pub fn handle_component_struct( - db: &dyn SyntaxGroup, - aux_data: &mut DojoAuxData, - struct_ast: ItemStruct, -) -> (RewriteNode, Vec) { - let mut diagnostics = vec![]; - - let members: Vec<_> = struct_ast - .members(db) - .elements(db) - .iter() - .map(|member| { - (member.name(db).text(db), member.type_clause(db).ty(db), member.has_attr(db, "key")) - }) - .collect::<_>(); - - let elements = struct_ast.members(db).elements(db); - let keys: Vec<_> = elements.iter().filter(|e| e.has_attr(db, "key")).collect::<_>(); - - if keys.is_empty() { - diagnostics.push(PluginDiagnostic { - message: "Component must define atleast one #[key] attribute".into(), - stable_ptr: struct_ast.name(db).stable_ptr().untyped(), - }); - } - - let key_names = keys.iter().map(|e| e.name(db).text(db)).join(", "); - - let key_types = - keys.iter().map(|e| e.type_clause(db).ty(db).as_syntax_node().get_text(db)).join(", "); - - let serialized_keys: Vec<_> = keys - .iter() - .map(|e| { - if e.type_clause(db).ty(db).as_syntax_node().get_text(db) == "felt252" { - return RewriteNode::Text(format!( - "array::ArrayTrait::append(ref serialized, {});\n", - e.name(db).text(db) - )); - } - - RewriteNode::Text(format!( - "serde::Serde::serialize(@{}, ref serialized);\n", - e.name(db).text(db) - )) - }) - .collect::<_>(); - - let component_serialized_keys: Vec<_> = keys - .iter() - .map(|e| { - if e.type_clause(db).ty(db).as_syntax_node().get_text(db) == "felt252" { - return RewriteNode::Text(format!( - "array::ArrayTrait::append(ref serialized, *self.{});\n", - e.name(db).text(db) - )); - } - - RewriteNode::Text(format!( - "serde::Serde::serialize(self.{}, ref serialized);\n", - e.name(db).text(db) - )) - }) - .collect::<_>(); - - let component_serialized_values: Vec<_> = elements - .iter() - .filter_map(|e| { - if !e.has_attr(db, "key") { - if e.type_clause(db).ty(db).as_syntax_node().get_text(db) == "felt252" { - return Some(RewriteNode::Text(format!( - "array::ArrayTrait::append(ref serialized, *self.{});\n", - e.name(db).text(db) - ))); - } - - return Some(RewriteNode::Text(format!( - "serde::Serde::serialize(self.{}, ref serialized);", - e.name(db).text(db) - ))); - } - - None - }) - .collect::<_>(); - - let schema = elements - .iter() - .map(|member| { - RewriteNode::interpolate_patched( - "array::ArrayTrait::append(ref arr, ('$name$', '$typ$', $is_key$));", - UnorderedHashMap::from([ - ( - "name".to_string(), - RewriteNode::new_trimmed(member.name(db).as_syntax_node()), - ), - ( - "typ".to_string(), - RewriteNode::new_trimmed(member.type_clause(db).ty(db).as_syntax_node()), - ), - ( - "is_key".to_string(), - RewriteNode::Text(member.has_attr(db, "key").to_string()), - ), - ]), - ) - }) - .collect::<_>(); - - let name = struct_ast.name(db).text(db); - aux_data.components.push(Component { - name: name.to_string(), - members: members - .iter() - .map(|(name, ty, key)| Member { - name: name.to_string(), - ty: ty.as_syntax_node().get_text(db).trim().to_string(), - key: *key, - }) - .collect(), - }); - - let member_prints: Vec<_> = members - .iter() - .map(|member| { - let member_name = &member.0; - format!( - "debug::PrintTrait::print('{}'); debug::PrintTrait::print(self.{});", - member_name, member_name - ) - }) - .collect(); - - let print_body = member_prints.join("\n"); - - ( - RewriteNode::interpolate_patched( - " - struct $type_name$ { - $members$ - } - - impl $type_name$Component of dojo::traits::Component<$type_name$> { - #[inline(always)] - fn name(self: @$type_name$) -> felt252 { - '$type_name$' - } - - #[inline(always)] - fn keys(self: @$type_name$) -> Span { - let mut serialized = ArrayTrait::new(); - $component_serialized_keys$ - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn values(self: @$type_name$) -> Span { - let mut serialized = ArrayTrait::new(); - $component_serialized_values$ - array::ArrayTrait::span(@serialized) - } - } - - #[cfg(test)] - impl $type_name$PrintImpl of debug::PrintTrait<$type_name$> { - fn print(self: $type_name$) { - $print_body$ - } - } - - #[starknet::interface] - trait I$type_name$ { - fn name(self: @T) -> felt252; - } - - #[starknet::contract] - mod $contract_name$ { - use super::$type_name$; - - #[storage] - struct Storage {} - - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - '$type_name$' - } - - #[external(v0)] - fn size(self: @ContractState) -> usize { - dojo::SerdeLen::<$type_name$>::len() - } - - #[external(v0)] - fn schema(self: @ContractState) -> Array<(felt252, felt252, bool)> { - let mut arr = array::ArrayTrait::new(); - $schema$ - arr - } - } - ", - UnorderedHashMap::from([ - ("contract_name".to_string(), RewriteNode::Text(name.to_case(Case::Snake))), - ( - "type_name".to_string(), - RewriteNode::new_trimmed(struct_ast.name(db).as_syntax_node()), - ), - ( - "members".to_string(), - RewriteNode::Copied(struct_ast.members(db).as_syntax_node()), - ), - ("key_names".to_string(), RewriteNode::Text(key_names)), - ("key_types".to_string(), RewriteNode::Text(key_types)), - ("serialized_keys".to_string(), RewriteNode::new_modified(serialized_keys)), - ( - "component_serialized_keys".to_string(), - RewriteNode::new_modified(component_serialized_keys), - ), - ( - "component_serialized_values".to_string(), - RewriteNode::new_modified(component_serialized_values), - ), - ("schema".to_string(), RewriteNode::new_modified(schema)), - ("print_body".to_string(), RewriteNode::Text(print_body)), - ]), - ), - diagnostics, - ) -} diff --git a/crates/dojo-lang/src/db.rs b/crates/dojo-lang/src/db.rs deleted file mode 100644 index 70eafb53df..0000000000 --- a/crates/dojo-lang/src/db.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use anyhow::Result; -use cairo_lang_compiler::db::{RootDatabase, RootDatabaseBuilder}; -use cairo_lang_filesystem::db::init_dev_corelib; -use cairo_lang_semantic::db::SemanticGroup; -use cairo_lang_semantic::plugin::SemanticPlugin; -use cairo_lang_starknet::plugin::StarkNetPlugin; - -use crate::plugin::DojoPlugin; - -pub const DOJOLIB_CRATE_NAME: &str = "dojo"; - -pub trait DojoRootDatabaseBuilderEx { - fn build_language_server( - &mut self, - path: PathBuf, - plugins: Vec>, - ) -> Result; - - /// Tunes a compiler database to Dojo (e.g. Dojo plugin). - fn with_dojo(&mut self) -> &mut Self; -} - -impl DojoRootDatabaseBuilderEx for RootDatabaseBuilder { - fn build_language_server( - &mut self, - path: PathBuf, - plugins: Vec>, - ) -> Result { - let mut db = RootDatabase::default(); - init_dev_corelib(&mut db, path); - db.set_semantic_plugins(plugins); - Ok(db) - } - - fn with_dojo(&mut self) -> &mut Self { - self.with_semantic_plugin(Arc::new(DojoPlugin)); - self.with_semantic_plugin(Arc::new(StarkNetPlugin::default())); - self - } -} diff --git a/crates/dojo-lang/src/inline_macro_plugin.rs b/crates/dojo-lang/src/inline_macro_plugin.rs deleted file mode 100644 index 2355197097..0000000000 --- a/crates/dojo-lang/src/inline_macro_plugin.rs +++ /dev/null @@ -1,104 +0,0 @@ -use cairo_lang_defs::plugin::PluginDiagnostic; -use cairo_lang_syntax::node::ast::{self}; -use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::kind::SyntaxKind; -use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode}; - -use crate::inline_macros::emit::EmitMacro; -use crate::inline_macros::get::GetMacro; -use crate::inline_macros::set::SetMacro; - -/// The result of expanding an inline macro. -#[derive(Debug, Default)] -pub struct InlineMacroExpanderData { - pub result_code: String, - pub code_changed: bool, - pub diagnostics: Vec, -} - -/// A trait for inline macros. -pub trait InlineMacro { - /// A function that appends the expanded code of the macro to the result code. - fn append_macro_code( - &self, - macro_expander_data: &mut InlineMacroExpanderData, - db: &dyn SyntaxGroup, - macro_arguments: &ast::ExprList, - ); - /// A function that returns true if the macro supports the given bracket type. - fn is_bracket_type_allowed( - &self, - db: &dyn SyntaxGroup, - macro_ast: &ast::ExprInlineMacro, - ) -> bool; -} - -/// Returns the inline macro plugin for the given macro name, or None if no such plugin exists. -fn get_inline_macro_plugin(macro_name: &str) -> Option> { - match macro_name { - "emit" => Some(Box::new(EmitMacro)), - "get" => Some(Box::new(GetMacro)), - "set" => Some(Box::new(SetMacro)), - _ => None, - } -} - -impl InlineMacroExpanderData { - /// Traverse the syntax tree, accumolates any non-macro code and expand all inline macros. - pub fn expand_node(&mut self, db: &dyn SyntaxGroup, syntax_node: &SyntaxNode) { - let node_kind = syntax_node.kind(db); - if let SyntaxKind::ExprInlineMacro = node_kind { - let inline_macro = ast::ExprInlineMacro::from_syntax_node(db, syntax_node.clone()); - self.handle_macro(db, &inline_macro); - } else { - if let Some(text) = syntax_node.text(db) { - self.result_code.push_str(&text); - } - for child in syntax_node.children(db) { - self.expand_node(db, &child); - } - } - } - - /// Expand a single inline macro. - fn handle_macro(&mut self, db: &dyn SyntaxGroup, inline_macro: &ast::ExprInlineMacro) { - let macro_name = inline_macro.path(db).as_syntax_node().get_text_without_trivia(db); - let macro_plugin = get_inline_macro_plugin(¯o_name); - - if let Some(macro_plugin) = macro_plugin { - if let Some(macro_arguments) = - self.extract_macro_args(db, macro_plugin.as_ref(), inline_macro) - { - macro_plugin.append_macro_code(self, db, ¯o_arguments); - } - } else { - self.result_code.push_str(&inline_macro.as_syntax_node().get_text(db)); - } - } - - /// Extract the macro arguments from the inline macro if the macro supports the given bracket - /// type. Otherwise, add a diagnostic. - fn extract_macro_args( - &mut self, - db: &dyn SyntaxGroup, - macro_plugin: &dyn InlineMacro, - macro_ast: &ast::ExprInlineMacro, - ) -> Option { - if macro_plugin.is_bracket_type_allowed(db, macro_ast) { - Some(match macro_ast.arguments(db) { - ast::WrappedExprList::BracketedExprList(expr_list) => expr_list.expressions(db), - ast::WrappedExprList::ParenthesizedExprList(expr_list) => expr_list.expressions(db), - ast::WrappedExprList::BracedExprList(expr_list) => expr_list.expressions(db), - }) - } else { - self.diagnostics.push(PluginDiagnostic { - stable_ptr: macro_ast.stable_ptr().untyped(), - message: format!( - "Macro {} does not support this bracket type", - macro_ast.path(db).as_syntax_node().get_text(db) - ), - }); - None - } - } -} diff --git a/crates/dojo-lang/src/inline_macros/emit.rs b/crates/dojo-lang/src/inline_macros/emit.rs index cefe782259..c0a08888bc 100644 --- a/crates/dojo-lang/src/inline_macros/emit.rs +++ b/crates/dojo-lang/src/inline_macros/emit.rs @@ -1,50 +1,65 @@ -use cairo_lang_defs::plugin::PluginDiagnostic; -use cairo_lang_syntax::node::TypedSyntaxNode; - -use crate::inline_macro_plugin::{InlineMacro, InlineMacroExpanderData}; +use cairo_lang_defs::patcher::PatchBuilder; +use cairo_lang_defs::plugin::{ + InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic, PluginGeneratedFile, +}; +use cairo_lang_semantic::inline_macros::unsupported_bracket_diagnostic; +use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; +#[derive(Debug)] pub struct EmitMacro; -impl InlineMacro for EmitMacro { - fn append_macro_code( +impl EmitMacro { + pub const NAME: &'static str = "emit"; +} +impl InlineMacroExprPlugin for EmitMacro { + fn generate_code( &self, - macro_expander_data: &mut InlineMacroExpanderData, db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, - macro_arguments: &cairo_lang_syntax::node::ast::ExprList, - ) { - let args = macro_arguments.elements(db); + syntax: &ast::ExprInlineMacro, + ) -> InlinePluginResult { + let ast::WrappedArgList::ParenthesizedArgList(arg_list) = syntax.arguments(db) else { + return unsupported_bracket_diagnostic(db, syntax); + }; + let mut builder = PatchBuilder::new(db); + builder.add_str( + "{ + let mut keys = Default::::default(); + let mut data = Default::::default();", + ); + + let args = arg_list.args(db).elements(db); if args.len() != 2 { - macro_expander_data.diagnostics.push(PluginDiagnostic { - message: "Invalid arguments. Expected \"emit!(world, event)\"".to_string(), - stable_ptr: macro_arguments.as_syntax_node().stable_ptr(), - }); - return; + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: arg_list.args(db).stable_ptr().untyped(), + message: "Invalid arguments. Expected \"emit!(world, event)\"".to_string(), + }], + }; } let world = &args[0]; let event = &args[1]; - let expanded_code = format!( - "{{ - let mut keys = Default::::default(); - let mut data = Default::::default(); - starknet::Event::append_keys_and_data(@{}, ref keys, ref data); - {}.emit(keys, data.span()); - }}", - event.as_syntax_node().get_text(db), - world.as_syntax_node().get_text(db), + + builder.add_str( + "\n starknet::Event::append_keys_and_data(@traits::Into::<_, Event>::into(", ); - macro_expander_data.result_code.push_str(&expanded_code); - macro_expander_data.code_changed = true; - } + builder.add_node(event.as_syntax_node()); + builder.add_str("), ref keys, ref data);"); - fn is_bracket_type_allowed( - &self, - db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, - macro_ast: &cairo_lang_syntax::node::ast::ExprInlineMacro, - ) -> bool { - matches!( - macro_ast.arguments(db), - cairo_lang_syntax::node::ast::WrappedExprList::ParenthesizedExprList(_) - ) + builder.add_str("\n "); + builder.add_node(world.as_syntax_node()); + builder.add_str(".emit(keys, data.span());"); + builder.add_str("}"); + + InlinePluginResult { + code: Some(PluginGeneratedFile { + name: "emit_inline_macro".into(), + content: builder.code, + diagnostics_mappings: builder.diagnostics_mappings, + aux_data: None, + }), + diagnostics: vec![], + } } } diff --git a/crates/dojo-lang/src/inline_macros/get.rs b/crates/dojo-lang/src/inline_macros/get.rs index 47319f55f8..c58b638479 100644 --- a/crates/dojo-lang/src/inline_macros/get.rs +++ b/crates/dojo-lang/src/inline_macros/get.rs @@ -1,96 +1,124 @@ -use cairo_lang_defs::plugin::PluginDiagnostic; +use cairo_lang_defs::patcher::PatchBuilder; +use cairo_lang_defs::plugin::{ + InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic, PluginGeneratedFile, +}; +use cairo_lang_semantic::inline_macros::unsupported_bracket_diagnostic; use cairo_lang_syntax::node::ast::Expr; -use cairo_lang_syntax::node::TypedSyntaxNode; +use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; use itertools::Itertools; -use super::{extract_components, CAIRO_ERR_MSG_LEN}; -use crate::inline_macro_plugin::{InlineMacro, InlineMacroExpanderData}; +use super::{extract_models, unsupported_arg_diagnostic, CAIRO_ERR_MSG_LEN}; +#[derive(Debug)] pub struct GetMacro; -impl InlineMacro for GetMacro { - fn append_macro_code( +impl GetMacro { + pub const NAME: &'static str = "get"; +} +impl InlineMacroExprPlugin for GetMacro { + fn generate_code( &self, - macro_expander_data: &mut InlineMacroExpanderData, db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, - macro_arguments: &cairo_lang_syntax::node::ast::ExprList, - ) { - let args = macro_arguments.elements(db); + syntax: &ast::ExprInlineMacro, + ) -> InlinePluginResult { + let ast::WrappedArgList::ParenthesizedArgList(arg_list) = syntax.arguments(db) else { + return unsupported_bracket_diagnostic(db, syntax); + }; + let mut builder = PatchBuilder::new(db); + builder.add_str( + "{ + let mut __get_macro_keys__ = array::ArrayTrait::new();", + ); + + let args = arg_list.args(db).elements(db); if args.len() != 3 { - macro_expander_data.diagnostics.push(PluginDiagnostic { - message: "Invalid arguments. Expected \"get!(world, keys, (components,))\"" - .to_string(), - stable_ptr: macro_arguments.as_syntax_node().stable_ptr(), - }); - return; + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: syntax.stable_ptr().untyped(), + message: "Invalid arguments. Expected \"get!(world, keys, (models,))\"" + .to_string(), + }], + }; } let world = &args[0]; - let keys = &args[1]; - let components = extract_components(db, &args[2]); - if components.is_empty() { - macro_expander_data.diagnostics.push(PluginDiagnostic { - message: "Component types cannot be empty".to_string(), - stable_ptr: macro_arguments.as_syntax_node().stable_ptr(), - }); - return; + let ast::ArgClause::Unnamed(keys) = args[1].arg_clause(db) else { + return unsupported_arg_diagnostic(db, syntax); + }; + + let ast::ArgClause::Unnamed(models) = args[2].arg_clause(db) else { + return unsupported_arg_diagnostic(db, syntax); + }; + let models = match extract_models(db, &models.value(db)) { + Ok(models) => models, + Err(diagnostic) => { + return InlinePluginResult { code: None, diagnostics: vec![diagnostic] }; + } + }; + + if models.is_empty() { + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: syntax.stable_ptr().untyped(), + message: "Model types cannot be empty".to_string(), + }], + }; } - let args = match keys { + let args = match keys.value(db) { Expr::Literal(literal) => format!("({})", literal.as_syntax_node().get_text(db)), _ => keys.as_syntax_node().get_text(db), }; - let mut expanded_code = format!( - "{{ - let mut __get_macro_keys__ = array::ArrayTrait::new(); - serde::Serde::serialize(@{args}, ref __get_macro_keys__); + builder.add_str(&format!( + "serde::Serde::serialize(@{args}, ref __get_macro_keys__); let __get_macro_keys__ = array::ArrayTrait::span(@__get_macro_keys__);" - ); + )); - for component in &components { - let mut lookup_err_msg = format!("{} not found", component.to_string()); + for model in &models { + let mut lookup_err_msg = format!("{} not found", model.to_string()); lookup_err_msg.truncate(CAIRO_ERR_MSG_LEN); - let mut deser_err_msg = format!("{} failed to deserialize", component.to_string()); + let mut deser_err_msg = format!("{} failed to deserialize", model.to_string()); deser_err_msg.truncate(CAIRO_ERR_MSG_LEN); - expanded_code.push_str(&format!( - "\n let __{component}_values__ = {}.entity('{component}', \ - __get_macro_keys__, 0_u8, dojo::SerdeLen::<{component}>::len()); - let mut __{component}_component__ = array::ArrayTrait::new(); - array::serialize_array_helper(__get_macro_keys__, ref __{component}_component__); - array::serialize_array_helper(__{component}_values__, ref \ - __{component}_component__); - let mut __{component}_component_span__ = \ - array::ArrayTrait::span(@__{component}_component__); - let __{component} = \ - option::OptionTrait::expect(serde::Serde::<{component}>::deserialize( - ref __{component}_component_span__ - ), '{deser_err_msg}');", + builder.add_str(&format!( + "\n let mut __{model}_layout__ = array::ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::<{model}>::layout(ref \ + __{model}_layout__); + let mut __{model}_layout_clone__ = __{model}_layout__.clone(); + let mut __{model}_layout_span__ = array::ArrayTrait::span(@__{model}_layout__); + let mut __{model}_layout_clone_span__ = \ + array::ArrayTrait::span(@__{model}_layout_clone__); + let mut __{model}_values__ = {}.entity('{model}', __get_macro_keys__, 0_u8, \ + dojo::packing::calculate_packed_size(ref __{model}_layout_clone_span__), \ + __{model}_layout_span__); + let mut __{model}_model__ = array::ArrayTrait::new(); + array::serialize_array_helper(__get_macro_keys__, ref __{model}_model__); + array::serialize_array_helper(__{model}_values__, ref __{model}_model__); + let mut __{model}_model_span__ = array::ArrayTrait::span(@__{model}_model__); + let __{model} = option::OptionTrait::expect(serde::Serde::<{model}>::deserialize( + ref __{model}_model_span__ + ), '{deser_err_msg}');\n", world.as_syntax_node().get_text(db), )); } - expanded_code.push_str( - format!( - "({}) + builder.add_str(&format!( + "({}) }}", - components.iter().map(|c| format!("__{c}")).join(",") - ) - .as_str(), - ); - macro_expander_data.result_code.push_str(&expanded_code); - macro_expander_data.code_changed = true; - } + models.iter().map(|c| format!("__{c}")).join(",") + )); - fn is_bracket_type_allowed( - &self, - db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, - macro_ast: &cairo_lang_syntax::node::ast::ExprInlineMacro, - ) -> bool { - matches!( - macro_ast.arguments(db), - cairo_lang_syntax::node::ast::WrappedExprList::ParenthesizedExprList(_) - ) + InlinePluginResult { + code: Some(PluginGeneratedFile { + name: "get_inline_macro".into(), + content: builder.code, + diagnostics_mappings: builder.diagnostics_mappings, + aux_data: None, + }), + diagnostics: vec![], + } } } diff --git a/crates/dojo-lang/src/inline_macros/mod.rs b/crates/dojo-lang/src/inline_macros/mod.rs index 25c4666cad..74aa1c9fb9 100644 --- a/crates/dojo-lang/src/inline_macros/mod.rs +++ b/crates/dojo-lang/src/inline_macros/mod.rs @@ -1,3 +1,4 @@ +use cairo_lang_defs::plugin::{InlinePluginResult, PluginDiagnostic}; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; use smol_str::SmolStr; @@ -8,38 +9,80 @@ pub mod set; const CAIRO_ERR_MSG_LEN: usize = 31; -pub fn extract_components(db: &dyn SyntaxGroup, expression: &ast::Expr) -> Vec { - let mut components = vec![]; +pub fn extract_models( + db: &dyn SyntaxGroup, + expression: &ast::Expr, +) -> Result, PluginDiagnostic> { + let mut models = vec![]; match expression { ast::Expr::Tuple(tuple) => { for element in tuple.expressions(db).elements(db) { - components.extend(extract_components(db, &element)); + match extract_models(db, &element) { + Ok(mut element_models) => models.append(&mut element_models), + Err(diagnostic) => return Err(diagnostic), + } } } ast::Expr::Parenthesized(parenthesized) => { - components.extend(extract_components(db, &parenthesized.expr(db))); + match extract_models(db, &parenthesized.expr(db)) { + Ok(mut parenthesized_models) => models.append(&mut parenthesized_models), + Err(diagnostic) => return Err(diagnostic), + } } ast::Expr::Path(path) => match path.elements(db).last().unwrap() { ast::PathSegment::WithGenericArgs(segment) => { let generic = segment.generic_args(db); for param in generic.generic_args(db).elements(db) { - if let ast::GenericArg::Expr(expr) = param { - components.extend(extract_components(db, &expr.value(db))); + let ast::GenericArg::Unnamed(unnamed) = param else { + return Err(PluginDiagnostic { + stable_ptr: param.stable_ptr().untyped(), + message: "Should be an unnamed argument".to_string(), + }); + }; + + let ast::GenericArgValue::Expr(expr) = unnamed.value(db) else { + return Err(PluginDiagnostic { + stable_ptr: unnamed.stable_ptr().untyped(), + message: "Should be an expression".to_string(), + }); + }; + + match extract_models(db, &expr.expr(db)) { + Ok(mut expr_models) => models.append(&mut expr_models), + Err(diagnostic) => return Err(diagnostic), } } } ast::PathSegment::Simple(segment) => { - components.push(segment.ident(db).text(db)); + models.push(segment.ident(db).text(db)); } }, _ => { - unimplemented!( - "Unsupported expression type: {}", - expression.as_syntax_node().get_text(db) - ); + return Err(PluginDiagnostic { + stable_ptr: expression.stable_ptr().untyped(), + message: format!( + "Unsupported expression type: {}", + expression.as_syntax_node().get_text(db) + ), + }); } } - components + Ok(models) +} +pub fn unsupported_arg_diagnostic( + db: &dyn SyntaxGroup, + macro_ast: &ast::ExprInlineMacro, +) -> InlinePluginResult { + InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: macro_ast.stable_ptr().untyped(), + message: format!( + "Macro {} does not support this arg type", + macro_ast.path(db).as_syntax_node().get_text(db) + ), + }], + } } diff --git a/crates/dojo-lang/src/inline_macros/set.rs b/crates/dojo-lang/src/inline_macros/set.rs index 0e889d3ddc..36ae5c7a8e 100644 --- a/crates/dojo-lang/src/inline_macros/set.rs +++ b/crates/dojo-lang/src/inline_macros/set.rs @@ -1,30 +1,50 @@ -use cairo_lang_defs::plugin::PluginDiagnostic; +use cairo_lang_defs::patcher::PatchBuilder; +use cairo_lang_defs::plugin::{ + InlineMacroExprPlugin, InlinePluginResult, PluginDiagnostic, PluginGeneratedFile, +}; +use cairo_lang_semantic::inline_macros::unsupported_bracket_diagnostic; use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; -use crate::inline_macro_plugin::{InlineMacro, InlineMacroExpanderData}; +use super::unsupported_arg_diagnostic; +#[derive(Debug)] pub struct SetMacro; -impl InlineMacro for SetMacro { - fn append_macro_code( +impl SetMacro { + pub const NAME: &'static str = "set"; +} +impl InlineMacroExprPlugin for SetMacro { + fn generate_code( &self, - macro_expander_data: &mut InlineMacroExpanderData, db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, - macro_arguments: &cairo_lang_syntax::node::ast::ExprList, - ) { - let args = macro_arguments.elements(db); + syntax: &ast::ExprInlineMacro, + ) -> InlinePluginResult { + let ast::WrappedArgList::ParenthesizedArgList(arg_list) = syntax.arguments(db) else { + return unsupported_bracket_diagnostic(db, syntax); + }; + let mut builder = PatchBuilder::new(db); + builder.add_str("{"); + + let args = arg_list.args(db).elements(db); if args.len() != 2 { - macro_expander_data.diagnostics.push(PluginDiagnostic { - message: "Invalid arguments. Expected \"(world, (components,))\"".to_string(), - stable_ptr: macro_arguments.as_syntax_node().stable_ptr(), - }); - return; + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: arg_list.args(db).stable_ptr().untyped(), + message: "Invalid arguments. Expected \"(world, (models,))\"".to_string(), + }], + }; } let world = &args[0]; + + let ast::ArgClause::Unnamed(models) = args[1].arg_clause(db) else { + return unsupported_arg_diagnostic(db, syntax); + }; + let mut bundle = vec![]; - match &args[1] { + match models.value(db) { ast::Expr::Parenthesized(parens) => { bundle.push(parens.expr(db).as_syntax_node().get_text(db)) } @@ -33,46 +53,47 @@ impl InlineMacro for SetMacro { }), ast::Expr::StructCtorCall(ctor) => bundle.push(ctor.as_syntax_node().get_text(db)), _ => { - macro_expander_data.diagnostics.push(PluginDiagnostic { - message: "Invalid arguments. Expected \"(world, (components,))\"".to_string(), - stable_ptr: macro_arguments.as_syntax_node().stable_ptr(), - }); - return; + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + message: "Invalid arguments. Expected \"(world, (models,))\"".to_string(), + stable_ptr: arg_list.args(db).stable_ptr().untyped(), + }], + }; } } if bundle.is_empty() { - macro_expander_data.diagnostics.push(PluginDiagnostic { - message: "Invalid arguments: No components provided.".to_string(), - stable_ptr: macro_arguments.as_syntax_node().stable_ptr(), - }); - return; + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + message: "Invalid arguments: No models provided.".to_string(), + stable_ptr: arg_list.args(db).stable_ptr().untyped(), + }], + }; } - let mut expanded_code = "{".to_string(); for entity in bundle { - expanded_code.push_str(&format!( + builder.add_str(&format!( "\n let __set_macro_value__ = {}; - {}.set_entity(dojo::traits::Component::name(@__set_macro_value__), \ - dojo::traits::Component::keys(@__set_macro_value__), 0_u8, \ - dojo::traits::Component::values(@__set_macro_value__));", + {}.set_entity(dojo::model::Model::name(@__set_macro_value__), \ + dojo::model::Model::keys(@__set_macro_value__), 0_u8, \ + dojo::model::Model::values(@__set_macro_value__), \ + dojo::model::Model::layout(@__set_macro_value__));", entity, world.as_syntax_node().get_text(db), )); } - expanded_code.push('}'); - macro_expander_data.result_code.push_str(&expanded_code); - macro_expander_data.code_changed = true; - } + builder.add_str("}"); - fn is_bracket_type_allowed( - &self, - db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, - macro_ast: &cairo_lang_syntax::node::ast::ExprInlineMacro, - ) -> bool { - matches!( - macro_ast.arguments(db), - cairo_lang_syntax::node::ast::WrappedExprList::ParenthesizedExprList(_) - ) + InlinePluginResult { + code: Some(PluginGeneratedFile { + name: "set_inline_macro".into(), + content: builder.code, + diagnostics_mappings: builder.diagnostics_mappings, + aux_data: None, + }), + diagnostics: vec![], + } } } diff --git a/crates/dojo-lang/src/introspect.rs b/crates/dojo-lang/src/introspect.rs new file mode 100644 index 0000000000..eeb5cc4b4d --- /dev/null +++ b/crates/dojo-lang/src/introspect.rs @@ -0,0 +1,112 @@ +use cairo_lang_defs::patcher::RewriteNode; +use cairo_lang_syntax::node::ast::ItemStruct; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::helpers::QueryAttrs; +use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode}; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use dojo_world::manifest::Member; +use itertools::Itertools; + +/// A handler for Dojo code derives Introspect for a struct +/// Parameters: +/// * db: The semantic database. +/// * struct_ast: The AST of the struct. +/// Returns: +/// * A RewriteNode containing the generated code. +pub fn handle_introspect_struct(db: &dyn SyntaxGroup, struct_ast: ItemStruct) -> RewriteNode { + let members: Vec<_> = struct_ast + .members(db) + .elements(db) + .iter() + .map(|member| Member { + name: member.name(db).text(db).to_string(), + ty: member.type_clause(db).ty(db).as_syntax_node().get_text(db).trim().to_string(), + key: member.has_attr(db, "key"), + }) + .collect::<_>(); + + let layout: Vec<_> = members + .iter() + .filter_map(|m| { + if m.key { + return None; + } + + Some(RewriteNode::Text(format!( + "dojo::database::schema::SchemaIntrospection::<{}>::layout(ref layout);\n", + m.ty + ))) + }) + .collect::<_>(); + + let member_types: Vec<_> = members + .iter() + .map(|m| { + let mut attrs = vec![]; + if m.key { + attrs.push("'key'") + } + + format!( + "dojo::database::schema::serialize_member(@dojo::database::schema::Member {{ + name: '{}', + ty: dojo::database::schema::SchemaIntrospection::<{}>::ty(), + attrs: array![{}].span() + }})", + m.name, + m.ty, + attrs.join(","), + ) + }) + .collect::<_>(); + + RewriteNode::interpolate_patched( + " + impl $name$SchemaIntrospection of dojo::database::schema::SchemaIntrospection<$name$> { + #[inline(always)] + fn size() -> usize { + $size$ + } + + #[inline(always)] + fn layout(ref layout: Array) { + $layout$ + } + + #[inline(always)] + fn ty() -> dojo::database::schema::Ty { + dojo::database::schema::Ty::Struct(dojo::database::schema::Struct { + name: '$name$', + attrs: array![].span(), + children: array![$member_types$].span() + }) + } + } + ", + UnorderedHashMap::from([ + ("name".to_string(), RewriteNode::new_trimmed(struct_ast.name(db).as_syntax_node())), + ( + "size".to_string(), + RewriteNode::Text( + struct_ast + .members(db) + .elements(db) + .iter() + .filter_map(|member| { + if member.has_attr(db, "key") { + return None; + } + + Some(format!( + "dojo::database::schema::SchemaIntrospection::<{}>::size()", + member.type_clause(db).ty(db).as_syntax_node().get_text(db), + )) + }) + .join(" + "), + ), + ), + ("layout".to_string(), RewriteNode::new_modified(layout)), + ("member_types".to_string(), RewriteNode::Text(member_types.join(","))), + ]), + ) +} diff --git a/crates/dojo-lang/src/lib.rs b/crates/dojo-lang/src/lib.rs index 94e68ba6f3..e761ad00f4 100644 --- a/crates/dojo-lang/src/lib.rs +++ b/crates/dojo-lang/src/lib.rs @@ -4,12 +4,11 @@ //! //! Learn more at [dojoengine.gg](http://dojoengine.gg). pub mod compiler; -pub mod component; -pub mod db; -pub mod inline_macro_plugin; -mod inline_macros; +pub mod inline_macros; +pub mod introspect; mod manifest; +pub mod model; pub mod plugin; -mod serde; +pub mod print; pub mod system; pub(crate) mod version; diff --git a/crates/dojo-lang/src/manifest.rs b/crates/dojo-lang/src/manifest.rs index 69396c432a..a02fc048bc 100644 --- a/crates/dojo-lang/src/manifest.rs +++ b/crates/dojo-lang/src/manifest.rs @@ -1,21 +1,18 @@ use std::collections::HashMap; -use anyhow::{Context, Result}; +use anyhow::Context; use cairo_lang_defs::ids::{ModuleId, ModuleItemId}; use cairo_lang_filesystem::ids::CrateId; use cairo_lang_semantic::db::SemanticGroup; -use cairo_lang_semantic::plugin::DynPluginAuxData; use cairo_lang_starknet::abi; +use cairo_lang_starknet::plugin::aux_data::StarkNetContractAuxData; use convert_case::{Case, Casing}; -use dojo_world::manifest::{ - Contract, Input, Output, System, EXECUTOR_CONTRACT_NAME, WORLD_CONTRACT_NAME, -}; -use itertools::Itertools; +use dojo_world::manifest::{Contract, EXECUTOR_CONTRACT_NAME, WORLD_CONTRACT_NAME}; use serde::Serialize; use smol_str::SmolStr; use starknet::core::types::FieldElement; -use crate::plugin::{DojoAuxData, SystemAuxData}; +use crate::plugin::DojoAuxData; #[derive(Default, Debug, Serialize)] pub(crate) struct Manifest(dojo_world::manifest::Manifest); @@ -27,7 +24,6 @@ impl Manifest { compiled_classes: HashMap)>, ) -> Self { let mut manifest = Manifest(dojo_world::manifest::Manifest::default()); - let (world, world_abi) = compiled_classes.get(WORLD_CONTRACT_NAME).unwrap_or_else(|| { panic!( "{}", @@ -71,49 +67,48 @@ impl Manifest { let Some(generated_file_info) = generated_file_info else { continue; }; - let Some(mapper) = - generated_file_info.aux_data.0.as_any().downcast_ref::() - else { - continue; - }; - let Some(aux_data) = mapper.0.as_any().downcast_ref::() else { + let Some(aux_data) = &generated_file_info.aux_data else { continue; }; - - manifest.find_components(db, aux_data, *module_id, &compiled_classes); - manifest.find_systems(db, aux_data, *module_id, &compiled_classes).unwrap(); + let aux_data = aux_data.0.as_any(); + if let Some(contracts) = aux_data.downcast_ref::() { + manifest.find_contracts(contracts, &compiled_classes); + } else if let Some(dojo_aux_data) = aux_data.downcast_ref() { + manifest.find_models(db, dojo_aux_data, *module_id, &compiled_classes); + } } } + manifest.filter_contracts(); } manifest } - /// Finds the inline modules annotated as components in the given crate_ids and - /// returns the corresponding Components. - fn find_components( + /// Finds the inline modules annotated as models in the given crate_ids and + /// returns the corresponding Models. + fn find_models( &mut self, db: &dyn SemanticGroup, aux_data: &DojoAuxData, module_id: ModuleId, compiled_classes: &HashMap)>, ) { - for component in &aux_data.components { - let component = component.clone(); - let name: SmolStr = component.name.clone().into(); + for model in &aux_data.models { + let model = model.clone(); + let name: SmolStr = model.name.clone().into(); if let Ok(Some(ModuleItemId::Struct(_))) = db.module_item_by_name(module_id, name.clone()) { - // It needs the `Component` suffix because we are + // It needs the `Model` suffix because we are // searching from the compiled contracts. let (class_hash, class_abi) = compiled_classes .get(name.to_case(Case::Snake).as_str()) - .with_context(|| format!("Component {name} not found in target.")) + .with_context(|| format!("Model {name} not found in target.")) .unwrap(); - self.0.components.push(dojo_world::manifest::Component { - name: component.name, - members: component.members, + self.0.models.push(dojo_world::manifest::Model { + name: model.name, + members: model.members, class_hash: *class_hash, abi: class_abi.clone(), }); @@ -121,69 +116,38 @@ impl Manifest { } } - fn find_systems( - &mut self, - db: &dyn SemanticGroup, - aux_data: &DojoAuxData, - module_id: ModuleId, - compiled_classes: &HashMap)>, - ) -> Result<()> { - for SystemAuxData { name, dependencies } in &aux_data.systems { - if let Ok(Some(ModuleItemId::Submodule(submodule_id))) = - db.module_item_by_name(module_id, name.clone()) - { - let defs_db = db.upcast(); - let fns = db.module_free_functions_ids(ModuleId::Submodule(submodule_id)).unwrap(); - for fn_id in fns { - if fn_id.name(defs_db) != "execute" { - continue; - } - let signature = db.free_function_signature(fn_id).unwrap(); + // removes contracts with DojoAuxType + fn filter_contracts(&mut self) { + let mut models = HashMap::new(); - let mut inputs = vec![]; - let mut params = signature.params; + for model in &self.0.models { + models.insert(model.class_hash, true); + } - // Last arg is always the `world_address` which is provided by the executor. - params.pop(); - for param in params.into_iter() { - let ty = param.ty.format(db); - // Context is injected by the executor contract. - if ty == "dojo::world::Context" { - continue; - } + for i in (0..self.0.contracts.len()).rev() { + if models.get(&self.0.contracts[i].class_hash).is_some() { + self.0.contracts.remove(i); + } + } + } - inputs.push(Input { name: param.id.name(db.upcast()).into(), ty }); - } + fn find_contracts( + &mut self, + aux_data: &StarkNetContractAuxData, + compiled_classes: &HashMap)>, + ) { + for name in &aux_data.contracts { + if "world" == name.as_str() || "executor" == name.as_str() { + return; + } - let outputs = if signature.return_type.is_unit(db) { - vec![] - } else { - vec![Output { ty: signature.return_type.format(db) }] - }; + let (class_hash, abi) = compiled_classes.get(name).unwrap().clone(); - let (class_hash, class_abi) = compiled_classes - .get(name.as_str()) - .with_context(|| format!("System {name} not found in target.")) - .unwrap(); - - self.0.systems.push(System { - name: name.clone(), - inputs, - outputs, - class_hash: *class_hash, - dependencies: dependencies - .iter() - .sorted_by(|a, b| a.name.cmp(&b.name)) - .cloned() - .collect::>(), - abi: class_abi.clone(), - }); - } - } else { - panic!("System `{name}` was not found."); - } + self.0.contracts.push(Contract { name: name.clone(), address: None, class_hash, abi }); } - - Ok(()) } } + +#[cfg(test)] +#[path = "manifest_test.rs"] +mod test; diff --git a/crates/dojo-lang/src/manifest_test.rs b/crates/dojo-lang/src/manifest_test.rs new file mode 100644 index 0000000000..41ae7428fe --- /dev/null +++ b/crates/dojo-lang/src/manifest_test.rs @@ -0,0 +1,36 @@ +use std::path::Path; +use std::{env, fs}; + +use cairo_lang_utils::ordered_hash_map::OrderedHashMap; +use dojo_test_utils::compiler::build_test_config; +use scarb::ops; + +cairo_lang_test_utils::test_file_test!( + manifest_file, + "src/manifest_test_data/", + { + manifest: "manifest", + }, + test_manifest_file +); + +pub fn test_manifest_file( + _inputs: &OrderedHashMap, + _args: &OrderedHashMap, +) -> Result, String> { + let config = + build_test_config("./src/manifest_test_data/example_ecs_crate/Scarb.toml").unwrap(); + let ws = ops::read_workspace(config.manifest_path(), &config).unwrap(); + + let packages = ws.members().map(|p| p.id).collect(); + ops::compile(packages, &ws).unwrap_or_else(|op| panic!("Error compiling: {op:?}")); + + let target_dir = config.target_dir().path_existent().unwrap(); + + let generated_manifest_path = + Path::new(target_dir).join(config.profile().as_str()).join("manifest.json"); + + let generated_file = fs::read_to_string(generated_manifest_path).unwrap(); + + Ok(OrderedHashMap::from([("expected_manifest_file".into(), generated_file)])) +} diff --git a/crates/dojo-lang/src/manifest_test_crate/.gitignore b/crates/dojo-lang/src/manifest_test_crate/.gitignore deleted file mode 100644 index 1de565933b..0000000000 --- a/crates/dojo-lang/src/manifest_test_crate/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/crates/dojo-lang/src/manifest_test_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_crate/Scarb.toml deleted file mode 100644 index a8d020d57d..0000000000 --- a/crates/dojo-lang/src/manifest_test_crate/Scarb.toml +++ /dev/null @@ -1,4 +0,0 @@ -[package] -cairo_version = "1.1.0" -name = "manifest_test" -version = "0.2.1" diff --git a/crates/dojo-lang/src/manifest_test_crate/src/lib.cairo b/crates/dojo-lang/src/manifest_test_crate/src/lib.cairo deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/dojo-lang/src/manifest_test_crate/src/lib.cairo +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/dojo-lang/src/manifest_test_data/example_ecs_crate b/crates/dojo-lang/src/manifest_test_data/example_ecs_crate new file mode 120000 index 0000000000..9e819a9d49 --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/example_ecs_crate @@ -0,0 +1 @@ +../../../../examples/ecs/ \ No newline at end of file diff --git a/crates/dojo-lang/src/manifest_test_data/manifest b/crates/dojo-lang/src/manifest_test_data/manifest new file mode 100644 index 0000000000..1721425fb2 --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/manifest @@ -0,0 +1,1098 @@ +//! > Test generated manifest file + +//! > test_runner_name +test_manifest_file + +//! > expected_manifest_file +{ + "world": { + "name": "world", + "address": null, + "class_hash": "0x260bc39baaf87e4310e5213178b5f7bea7344b9806b9d08101ec066aa7bc18", + "abi": [ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::IWorld" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::>", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::>" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::IWorld", + "items": [ + { + "type": "function", + "name": "model", + "inputs": [ + { + "name": "name", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit", + "inputs": [ + { + "name": "keys", + "type": "core::array::Array::" + }, + { + "name": "values", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "length", + "type": "core::integer::u32" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "offset", + "type": "core::integer::u8" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entities", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "index", + "type": "core::felt252" + }, + { + "name": "length", + "type": "core::integer::u32" + }, + { + "name": "layout", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "(core::array::Span::, core::array::Span::>)" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_executor", + "inputs": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "executor", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "target", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "target", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "target", + "type": "core::felt252" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "model", + "type": "core::felt252" + }, + { + "name": "system", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "executor", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "caller", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "offset", + "type": "core::integer::u8", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world::StoreDelRecord", + "kind": "nested" + } + ] + } + ] + }, + "executor": { + "name": "executor", + "address": null, + "class_hash": "0x2b35dd4816731188ed1ad16caa73bde76075c9d9cb8cbfa3e447d3ab9b1ab33", + "abi": [ + { + "type": "impl", + "name": "Executor", + "interface_name": "dojo::executor::IExecutor" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "interface", + "name": "dojo::executor::IExecutor", + "items": [ + { + "type": "function", + "name": "call", + "inputs": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + }, + { + "name": "entrypoint", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "event", + "name": "dojo::executor::executor::Event", + "kind": "enum", + "variants": [] + } + ] + }, + "systems": [], + "contracts": [ + { + "name": "player_actions", + "address": null, + "class_hash": "0x4b8aedc50c01814ad20b7e976f9d4fe76d13f56fa57aabd2a6399c7fbc1a187", + "abi": [ + { + "type": "impl", + "name": "PlayerActionsImpl", + "interface_name": "dojo_examples::systems::with_decorator::IPlayerActions" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "enum", + "name": "dojo_examples::models::Direction", + "variants": [ + { + "name": "None", + "type": "()" + }, + { + "name": "Left", + "type": "()" + }, + { + "name": "Right", + "type": "()" + }, + { + "name": "Up", + "type": "()" + }, + { + "name": "Down", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo_examples::systems::with_decorator::IPlayerActions", + "items": [ + { + "type": "function", + "name": "spawn", + "inputs": [ + { + "name": "world", + "type": "dojo::world::IWorldDispatcher" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "move", + "inputs": [ + { + "name": "world", + "type": "dojo::world::IWorldDispatcher" + }, + { + "name": "direction", + "type": "dojo_examples::models::Direction" + } + ], + "outputs": [], + "state_mutability": "view" + } + ] + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "event", + "name": "dojo_examples::systems::with_decorator::player_actions::Moved", + "kind": "struct", + "members": [ + { + "name": "player", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "direction", + "type": "dojo_examples::models::Direction", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo_examples::systems::with_decorator::player_actions::Event", + "kind": "enum", + "variants": [ + { + "name": "Moved", + "type": "dojo_examples::systems::with_decorator::player_actions::Moved", + "kind": "nested" + } + ] + } + ] + }, + { + "name": "player_actions_external", + "address": null, + "class_hash": "0x49a96abd2a2e1148a290a7e0aa101e17653d0990456c6a400614534e3696e49", + "abi": [ + { + "type": "impl", + "name": "PlayerActionsImpl", + "interface_name": "dojo_examples::systems::raw_contract::IPlayerActions" + }, + { + "type": "struct", + "name": "dojo::world::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "enum", + "name": "dojo_examples::models::Direction", + "variants": [ + { + "name": "None", + "type": "()" + }, + { + "name": "Left", + "type": "()" + }, + { + "name": "Right", + "type": "()" + }, + { + "name": "Up", + "type": "()" + }, + { + "name": "Down", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo_examples::systems::raw_contract::IPlayerActions", + "items": [ + { + "type": "function", + "name": "spawn", + "inputs": [ + { + "name": "world", + "type": "dojo::world::IWorldDispatcher" + } + ], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "function", + "name": "move", + "inputs": [ + { + "name": "world", + "type": "dojo::world::IWorldDispatcher" + }, + { + "name": "direction", + "type": "dojo_examples::models::Direction" + } + ], + "outputs": [], + "state_mutability": "view" + } + ] + }, + { + "type": "event", + "name": "dojo_examples::systems::raw_contract::player_actions_external::Moved", + "kind": "struct", + "members": [ + { + "name": "player", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "direction", + "type": "dojo_examples::models::Direction", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo_examples::systems::raw_contract::player_actions_external::Event", + "kind": "enum", + "variants": [ + { + "name": "Moved", + "type": "dojo_examples::systems::raw_contract::player_actions_external::Moved", + "kind": "nested" + } + ] + } + ] + } + ], + "models": [ + { + "name": "Moves", + "members": [ + { + "name": "player", + "type": "ContractAddress", + "key": true + }, + { + "name": "remaining", + "type": "u8", + "key": false + }, + { + "name": "last_direction", + "type": "Direction", + "key": false + } + ], + "class_hash": "0x3fc154aaf236e0478a5ea100b3d1971b30c70c14fa0e1a25606973ec75247a4", + "abi": [ + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "unpacked_size", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "packed_size", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "view" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "function", + "name": "layout", + "inputs": [], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::>", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::>" + } + ] + }, + { + "type": "struct", + "name": "dojo::database::schema::Struct", + "members": [ + { + "name": "name", + "type": "core::felt252" + }, + { + "name": "attrs", + "type": "core::array::Span::" + }, + { + "name": "children", + "type": "core::array::Span::>" + } + ] + }, + { + "type": "struct", + "name": "dojo::database::schema::EnumMember", + "members": [ + { + "name": "name", + "type": "core::felt252" + }, + { + "name": "attrs", + "type": "core::array::Span::" + }, + { + "name": "values", + "type": "core::array::Span::>" + } + ] + }, + { + "type": "enum", + "name": "dojo::database::schema::Ty", + "variants": [ + { + "name": "Simple", + "type": "core::felt252" + }, + { + "name": "Struct", + "type": "dojo::database::schema::Struct" + }, + { + "name": "Enum", + "type": "dojo::database::schema::EnumMember" + } + ] + }, + { + "type": "function", + "name": "schema", + "inputs": [], + "outputs": [ + { + "type": "dojo::database::schema::Ty" + } + ], + "state_mutability": "view" + }, + { + "type": "event", + "name": "dojo_examples::models::moves::Event", + "kind": "enum", + "variants": [] + } + ] + }, + { + "name": "Position", + "members": [ + { + "name": "player", + "type": "ContractAddress", + "key": true + }, + { + "name": "vec", + "type": "Vec2", + "key": false + } + ], + "class_hash": "0x7a812f2cfb414d5aa04bb9a3c91cdcaf1d30e193bd6cb7faf9b7c294722fab4", + "abi": [ + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "unpacked_size", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "packed_size", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "view" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "function", + "name": "layout", + "inputs": [], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::>", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::>" + } + ] + }, + { + "type": "struct", + "name": "dojo::database::schema::Struct", + "members": [ + { + "name": "name", + "type": "core::felt252" + }, + { + "name": "attrs", + "type": "core::array::Span::" + }, + { + "name": "children", + "type": "core::array::Span::>" + } + ] + }, + { + "type": "struct", + "name": "dojo::database::schema::EnumMember", + "members": [ + { + "name": "name", + "type": "core::felt252" + }, + { + "name": "attrs", + "type": "core::array::Span::" + }, + { + "name": "values", + "type": "core::array::Span::>" + } + ] + }, + { + "type": "enum", + "name": "dojo::database::schema::Ty", + "variants": [ + { + "name": "Simple", + "type": "core::felt252" + }, + { + "name": "Struct", + "type": "dojo::database::schema::Struct" + }, + { + "name": "Enum", + "type": "dojo::database::schema::EnumMember" + } + ] + }, + { + "type": "function", + "name": "schema", + "inputs": [], + "outputs": [ + { + "type": "dojo::database::schema::Ty" + } + ], + "state_mutability": "view" + }, + { + "type": "event", + "name": "dojo_examples::models::position::Event", + "kind": "enum", + "variants": [] + } + ] + } + ] +} diff --git a/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml new file mode 100644 index 0000000000..4ebbfc58da --- /dev/null +++ b/crates/dojo-lang/src/manifest_test_data/simple_crate/Scarb.toml @@ -0,0 +1,10 @@ +[package] +cairo-version = "2.2.0" +name = "test_crate" +version = "0.2.1" + +[cairo] +sierra-replace-ids = true + +[dependencies] +dojo = { path = "../../../../dojo-core" } diff --git a/packages/core/changelog.md b/crates/dojo-lang/src/manifest_test_data/simple_crate/src/lib.cairo similarity index 100% rename from packages/core/changelog.md rename to crates/dojo-lang/src/manifest_test_data/simple_crate/src/lib.cairo diff --git a/crates/dojo-lang/src/model.rs b/crates/dojo-lang/src/model.rs new file mode 100644 index 0000000000..fc62785b54 --- /dev/null +++ b/crates/dojo-lang/src/model.rs @@ -0,0 +1,168 @@ +use cairo_lang_defs::patcher::RewriteNode; +use cairo_lang_defs::plugin::PluginDiagnostic; +use cairo_lang_syntax::node::ast::ItemStruct; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::helpers::QueryAttrs; +use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode}; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use convert_case::{Case, Casing}; +use dojo_world::manifest::Member; + +use crate::introspect::handle_introspect_struct; +use crate::plugin::{DojoAuxData, Model}; + +/// A handler for Dojo code that modifies a model struct. +/// Parameters: +/// * db: The semantic database. +/// * struct_ast: The AST of the model struct. +/// Returns: +/// * A RewriteNode containing the generated code. +pub fn handle_model_struct( + db: &dyn SyntaxGroup, + aux_data: &mut DojoAuxData, + struct_ast: ItemStruct, +) -> (RewriteNode, Vec) { + let mut diagnostics = vec![]; + + let elements = struct_ast.members(db).elements(db); + let members: &Vec<_> = &elements + .iter() + .map(|member| Member { + name: member.name(db).text(db).to_string(), + ty: member.type_clause(db).ty(db).as_syntax_node().get_text(db).trim().to_string(), + key: member.has_attr(db, "key"), + }) + .collect::<_>(); + + let keys: Vec<_> = members.iter().filter(|m| m.key).collect::<_>(); + + if keys.is_empty() { + diagnostics.push(PluginDiagnostic { + message: "Model must define atleast one #[key] attribute".into(), + stable_ptr: struct_ast.name(db).stable_ptr().untyped(), + }); + } + + let serialize_member = |m: &Member, include_key: bool| { + if m.key && !include_key { + return None; + } + + if m.ty == "felt252" { + return Some(RewriteNode::Text(format!( + "array::ArrayTrait::append(ref serialized, *self.{});", + m.name + ))); + } + + Some(RewriteNode::Text(format!( + "serde::Serde::serialize(self.{}, ref serialized);", + m.name + ))) + }; + + let serialized_keys: Vec<_> = + keys.iter().filter_map(|m| serialize_member(m, true)).collect::<_>(); + + let serialized_values: Vec<_> = + members.iter().filter_map(|m| serialize_member(m, false)).collect::<_>(); + + let name = struct_ast.name(db).text(db); + aux_data.models.push(Model { name: name.to_string(), members: members.to_vec() }); + + ( + RewriteNode::interpolate_patched( + " + impl $type_name$Model of dojo::model::Model<$type_name$> { + #[inline(always)] + fn name(self: @$type_name$) -> felt252 { + '$type_name$' + } + + #[inline(always)] + fn keys(self: @$type_name$) -> Span { + let mut serialized = ArrayTrait::new(); + $serialized_keys$ + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn values(self: @$type_name$) -> Span { + let mut serialized = ArrayTrait::new(); + $serialized_values$ + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn layout(self: @$type_name$) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::<$type_name$>::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[inline(always)] + fn packed_size(self: @$type_name$) -> usize { + let mut layout = self.layout(); + dojo::packing::calculate_packed_size(ref layout) + } + } + + $schema_introspection$ + + #[starknet::interface] + trait I$type_name$ { + fn name(self: @T) -> felt252; + } + + #[starknet::contract] + mod $contract_name$ { + use super::$type_name$; + + #[storage] + struct Storage {} + + #[external(v0)] + fn name(self: @ContractState) -> felt252 { + '$type_name$' + } + + #[external(v0)] + fn unpacked_size(self: @ContractState) -> usize { + dojo::database::schema::SchemaIntrospection::<$type_name$>::size() + } + + #[external(v0)] + fn packed_size(self: @ContractState) -> usize { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::<$type_name$>::layout(ref layout); + let mut layout_span = layout.span(); + dojo::packing::calculate_packed_size(ref layout_span) + } + + #[external(v0)] + fn layout(self: @ContractState) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::<$type_name$>::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[external(v0)] + fn schema(self: @ContractState) -> dojo::database::schema::Ty { + dojo::database::schema::SchemaIntrospection::<$type_name$>::ty() + } + } + ", + UnorderedHashMap::from([ + ("contract_name".to_string(), RewriteNode::Text(name.to_case(Case::Snake))), + ( + "type_name".to_string(), + RewriteNode::new_trimmed(struct_ast.name(db).as_syntax_node()), + ), + ("schema_introspection".to_string(), handle_introspect_struct(db, struct_ast)), + ("serialized_keys".to_string(), RewriteNode::new_modified(serialized_keys)), + ("serialized_values".to_string(), RewriteNode::new_modified(serialized_values)), + ]), + ), + diagnostics, + ) +} diff --git a/crates/dojo-lang/src/plugin.rs b/crates/dojo-lang/src/plugin.rs index 679d3bb11f..4cf4656835 100644 --- a/crates/dojo-lang/src/plugin.rs +++ b/crates/dojo-lang/src/plugin.rs @@ -1,41 +1,38 @@ use std::sync::Arc; +use anyhow::Result; +use cairo_lang_defs::patcher::PatchBuilder; use cairo_lang_defs::plugin::{ - DynGeneratedFileAuxData, GeneratedFileAuxData, MacroPlugin, PluginDiagnostic, - PluginGeneratedFile, PluginResult, + DynGeneratedFileAuxData, GeneratedFileAuxData, InlineMacroExprPlugin, MacroPlugin, + PluginDiagnostic, PluginGeneratedFile, PluginResult, }; -use cairo_lang_diagnostics::DiagnosticEntry; -use cairo_lang_semantic::db::SemanticGroup; -use cairo_lang_semantic::patcher::{PatchBuilder, Patches}; -use cairo_lang_semantic::plugin::{ - AsDynGeneratedFileAuxData, AsDynMacroPlugin, DynPluginAuxData, PluginAuxData, - PluginMappedDiagnostic, SemanticPlugin, TrivialPluginAuxData, -}; -use cairo_lang_semantic::SemanticDiagnostic; -use cairo_lang_starknet::plugin::StarkNetPlugin; use cairo_lang_syntax::attribute::structured::{ AttributeArg, AttributeArgVariant, AttributeStructurize, }; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::helpers::QueryAttrs; -use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; -use dojo_types::component::Member; +use cairo_lang_syntax::node::{ast, Terminal}; use dojo_types::system::Dependency; -use scarb::compiler::plugin::builtin::BuiltinSemanticCairoPlugin; +use dojo_world::manifest::Member; +use scarb::compiler::plugin::builtin::BuiltinStarkNetPlugin; +use scarb::compiler::plugin::{CairoPlugin, CairoPluginInstance}; use scarb::core::{PackageId, PackageName, SourceId}; use semver::Version; use smol_str::SmolStr; use url::Url; -use crate::component::handle_component_struct; -use crate::inline_macro_plugin::InlineMacroExpanderData; -use crate::serde::handle_serde_len_struct; +use crate::inline_macros::emit::EmitMacro; +use crate::inline_macros::get::GetMacro; +use crate::inline_macros::set::SetMacro; +use crate::introspect::handle_introspect_struct; +use crate::model::handle_model_struct; +use crate::print::derive_print; use crate::system::System; const SYSTEM_ATTR: &str = "system"; #[derive(Clone, Debug, PartialEq)] -pub struct Component { +pub struct Model { pub name: String, pub members: Vec, } @@ -49,12 +46,9 @@ pub struct SystemAuxData { /// Dojo related auxiliary data of the Dojo plugin. #[derive(Debug, Default, PartialEq)] pub struct DojoAuxData { - /// Patches of code that need translation in case they have diagnostics. - pub patches: Patches, - - /// A list of components that were processed by the plugin. - pub components: Vec, - /// A list of systems that were processed by the plugin and their component dependencies. + /// A list of models that were processed by the plugin. + pub models: Vec, + /// A list of systems that were processed by the plugin and their model dependencies. pub systems: Vec, } impl GeneratedFileAuxData for DojoAuxData { @@ -65,26 +59,6 @@ impl GeneratedFileAuxData for DojoAuxData { if let Some(other) = other.as_any().downcast_ref::() { self == other } else { false } } } -impl AsDynGeneratedFileAuxData for DojoAuxData { - fn as_dyn_macro_token(&self) -> &(dyn GeneratedFileAuxData + 'static) { - self - } -} -impl PluginAuxData for DojoAuxData { - fn map_diag( - &self, - db: &(dyn SemanticGroup + 'static), - diag: &dyn std::any::Any, - ) -> Option { - let Some(diag) = diag.downcast_ref::() else { - return None; - }; - let span = self - .patches - .translate(db.upcast(), diag.stable_location.diagnostic_location(db.upcast()).span)?; - Some(PluginMappedDiagnostic { span, message: diag.format(db) }) - } -} #[cfg(test)] #[path = "plugin_test.rs"] @@ -103,28 +77,38 @@ impl DojoPlugin { } } +impl CairoPlugin for DojoPlugin { + fn id(&self) -> PackageId { + let url = Url::parse("https://github.com/dojoengine/dojo").unwrap(); + PackageId::new( + PackageName::new("dojo_plugin"), + Version::parse("0.2.1").unwrap(), + SourceId::for_git(&url, &scarb::core::GitReference::DefaultBranch).unwrap(), + ) + } + + fn instantiate(&self) -> Result> { + Ok(Box::new(DojoPluginInstance)) + } +} + +struct DojoPluginInstance; +impl CairoPluginInstance for DojoPluginInstance { + fn macro_plugins(&self) -> Vec> { + vec![Arc::new(DojoPlugin)] + } + + fn inline_macro_plugins(&self) -> Vec<(String, Arc)> { + vec![ + (GetMacro::NAME.into(), Arc::new(GetMacro)), + (SetMacro::NAME.into(), Arc::new(SetMacro)), + (EmitMacro::NAME.into(), Arc::new(EmitMacro)), + ] + } +} + impl MacroPlugin for DojoPlugin { fn generate_code(&self, db: &dyn SyntaxGroup, item_ast: ast::Item) -> PluginResult { - let mut expander_data = InlineMacroExpanderData::default(); - expander_data.expand_node(db, &item_ast.as_syntax_node()); - if expander_data.code_changed { - return PluginResult { - code: Some(PluginGeneratedFile { - name: "inline_macros".into(), - content: expander_data.result_code.clone(), - aux_data: DynGeneratedFileAuxData(Arc::new(TrivialPluginAuxData {})), - }), - diagnostics: expander_data.diagnostics, - remove_original_item: true, - }; - } else if !expander_data.diagnostics.is_empty() { - return PluginResult { - code: None, - diagnostics: expander_data.diagnostics, - remove_original_item: false, - }; - } - match item_ast { ast::Item::Module(module_ast) => self.handle_mod(db, module_ast), ast::Item::Struct(struct_ast) => { @@ -166,18 +150,22 @@ impl MacroPlugin for DojoPlugin { continue; }; - // Get the text of the segment and check if it is "Component" + // Get the text of the segment and check if it is "Model" let derived = segment.ident(db).text(db); match derived.as_str() { - "Component" => { - let (component_rewrite_nodes, component_diagnostics) = - handle_component_struct(db, &mut aux_data, struct_ast.clone()); - rewrite_nodes.push(component_rewrite_nodes); - diagnostics.extend(component_diagnostics); + "Model" => { + let (model_rewrite_nodes, model_diagnostics) = + handle_model_struct(db, &mut aux_data, struct_ast.clone()); + rewrite_nodes.push(model_rewrite_nodes); + diagnostics.extend(model_diagnostics); + } + "Print" => { + rewrite_nodes.push(derive_print(db, struct_ast.clone())); } - "SerdeLen" => { - rewrite_nodes.push(handle_serde_len_struct(db, struct_ast.clone())); + "Introspect" => { + rewrite_nodes + .push(handle_introspect_struct(db, struct_ast.clone())); } _ => continue, } @@ -198,10 +186,11 @@ impl MacroPlugin for DojoPlugin { code: Some(PluginGeneratedFile { name, content: builder.code, - aux_data: DynGeneratedFileAuxData::new(DynPluginAuxData::new(aux_data)), + aux_data: Some(DynGeneratedFileAuxData::new(aux_data)), + diagnostics_mappings: builder.diagnostics_mappings, }), diagnostics, - remove_original_item: true, + remove_original_item: false, } } _ => PluginResult::default(), @@ -209,35 +198,13 @@ impl MacroPlugin for DojoPlugin { } } -impl AsDynMacroPlugin for DojoPlugin { - fn as_dyn_macro_plugin<'a>(self: Arc) -> Arc - where - Self: 'a, - { - self - } -} -impl SemanticPlugin for DojoPlugin {} - pub struct CairoPluginRepository(scarb::compiler::plugin::CairoPluginRepository); impl CairoPluginRepository { pub fn new() -> Self { let mut repo = scarb::compiler::plugin::CairoPluginRepository::empty(); - let url = Url::parse("https://github.com/dojoengine/dojo").unwrap(); - let dojo_package_id = PackageId::new( - PackageName::new("dojo_plugin"), - Version::parse("0.2.1").unwrap(), - SourceId::for_git(&url, &scarb::core::GitReference::DefaultBranch).unwrap(), - ); - repo.add(Box::new(BuiltinSemanticCairoPlugin::::new(dojo_package_id))).unwrap(); - let starknet_package_id = PackageId::new( - PackageName::STARKNET, - Version::parse("2.1.1").unwrap(), - SourceId::for_std(), - ); - repo.add(Box::new(BuiltinSemanticCairoPlugin::::new(starknet_package_id))) - .unwrap(); + repo.add(Box::new(DojoPlugin)).unwrap(); + repo.add(Box::new(BuiltinStarkNetPlugin)).unwrap(); Self(repo) } } diff --git a/crates/dojo-lang/src/plugin_test.rs b/crates/dojo-lang/src/plugin_test.rs index f0971bea58..7429e9c7dd 100644 --- a/crates/dojo-lang/src/plugin_test.rs +++ b/crates/dojo-lang/src/plugin_test.rs @@ -1,54 +1,48 @@ use std::sync::Arc; -use cairo_lang_defs::plugin::PluginGeneratedFile; +use cairo_lang_defs::plugin::{MacroPlugin, PluginGeneratedFile}; use cairo_lang_diagnostics::{format_diagnostics, DiagnosticLocation}; -use cairo_lang_filesystem::db::{FilesDatabase, FilesGroup}; +use cairo_lang_filesystem::cfg::CfgSet; +use cairo_lang_filesystem::db::FilesGroup; use cairo_lang_formatter::format_string; use cairo_lang_parser::test_utils::create_virtual_file; use cairo_lang_parser::utils::{get_syntax_file_and_diagnostics, SimpleParserDatabase}; -use cairo_lang_semantic::plugin::SemanticPlugin; -use cairo_lang_syntax::node::db::SyntaxDatabase; use cairo_lang_syntax::node::TypedSyntaxNode; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; -use cairo_lang_utils::Upcast; use crate::plugin::DojoPlugin; -#[salsa::database(SyntaxDatabase, FilesDatabase)] -#[derive(Default)] -pub struct DatabaseImpl { - storage: salsa::Storage, -} -impl salsa::Database for DatabaseImpl {} -impl Upcast for DatabaseImpl { - fn upcast(&self) -> &(dyn FilesGroup + 'static) { - self - } -} - cairo_lang_test_utils::test_file_test!( expand_plugin, "src/plugin_test_data", { - component: "component", + model: "model", + print: "print", + introspect: "introspect", system: "system", - inline_macros: "inline_macros", }, test_expand_plugin ); pub fn test_expand_plugin( inputs: &OrderedHashMap, -) -> OrderedHashMap { + _args: &OrderedHashMap, +) -> Result, String> { let db = &mut SimpleParserDatabase::default(); + + let cfg_set: Option = + inputs.get("cfg").map(|s| serde_json::from_str(s.as_str()).unwrap()); + if let Some(cfg_set) = cfg_set { + db.set_cfg_set(Arc::new(cfg_set)); + } + let cairo_code = &inputs["cairo_code"]; let file_id = create_virtual_file(db, "dummy_file.cairo", cairo_code); let (syntax_file, diagnostics) = get_syntax_file_and_diagnostics(db, file_id, cairo_code); assert!(diagnostics.is_empty(), "Unexpected diagnostics:\n{}", diagnostics.format(db)); - let file_syntax_node = syntax_file.as_syntax_node(); - let plugins: Vec> = vec![Arc::new(DojoPlugin)]; + let plugins: Vec> = vec![Arc::new(DojoPlugin)]; let mut generated_items: Vec = Vec::new(); let mut diagnostic_items: Vec = Vec::new(); @@ -56,10 +50,10 @@ pub fn test_expand_plugin( let mut remove_original_item = false; let mut local_generated_items = Vec::::new(); for plugin in &plugins { - let result = plugin.clone().as_dyn_macro_plugin().generate_code(db, item.clone()); + let result = plugin.generate_code(db, item.clone()); diagnostic_items.extend(result.diagnostics.iter().map(|diag| { - let syntax_node = file_syntax_node.lookup_ptr(db, diag.stable_ptr); + let syntax_node = diag.stable_ptr.lookup(db); let location = DiagnosticLocation { file_id, span: syntax_node.span_without_trivia(db) }; @@ -87,8 +81,8 @@ pub fn test_expand_plugin( generated_items.extend(local_generated_items); } - OrderedHashMap::from([ + Ok(OrderedHashMap::from([ ("generated_cairo_code".into(), generated_items.join("\n")), ("expected_diagnostics".into(), diagnostic_items.join("\n")), - ]) + ])) } diff --git a/crates/dojo-lang/src/plugin_test_data/component b/crates/dojo-lang/src/plugin_test_data/component deleted file mode 100644 index 75c620765a..0000000000 --- a/crates/dojo-lang/src/plugin_test_data/component +++ /dev/null @@ -1,327 +0,0 @@ -//! > Test expansion of the component contract. - -//! > test_runner_name -test_expand_plugin - -//! > cairo_code -use serde::Serde; - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Position { - #[key] - id: felt252, - - x: felt252, - y: felt252 -} - -trait PositionTrait { - fn is_zero(self: Position) -> bool; - fn is_equal(self: Position, b: Position) -> bool; -} - -impl PositionImpl of PositionTrait { - fn is_zero(self: Position) -> bool { - match self.x - self.y { - 0 => bool::True(()), - _ => bool::False(()), - } - } - - fn is_equal(self: Position, b: Position) -> bool { - self.x == b.x && self.y == b.y - } -} - -#[derive(Component, Serde)] -struct Roles { - role_ids: Array -} - -impl RolesSerdeLen of dojo::SerdeLen { - #[inline(always)] - fn len() -> usize { - 5 - } -} - -use starknet::ContractAddress; - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Player { - #[key] - game: felt252, - #[key] - player: ContractAddress, - - name: felt252, -} - -//! > generated_cairo_code -use serde::Serde; - -struct Position { - #[key] - id: felt252, - x: felt252, - y: felt252 -} - -impl PositionComponent of dojo::traits::Component { - #[inline(always)] - fn name(self: @Position) -> felt252 { - 'Position' - } - - #[inline(always)] - fn keys(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - array::ArrayTrait::append(ref serialized, *self.id); - - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn values(self: @Position) -> Span { - let mut serialized = ArrayTrait::new(); - array::ArrayTrait::append(ref serialized, *self.x); - serde::Serde::serialize(self.y, ref serialized); - array::ArrayTrait::span(@serialized) - } -} - -#[cfg(test)] -impl PositionPrintImpl of debug::PrintTrait { - fn print(self: Position) { - debug::PrintTrait::print('id'); - debug::PrintTrait::print(self.id); - debug::PrintTrait::print('x'); - debug::PrintTrait::print(self.x); - debug::PrintTrait::print('y'); - debug::PrintTrait::print(self.y); - } -} - -#[starknet::interface] -trait IPosition { - fn name(self: @T) -> felt252; -} - -#[starknet::contract] -mod position { - use super::Position; - - #[storage] - struct Storage {} - - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - 'Position' - } - - #[external(v0)] - fn size(self: @ContractState) -> usize { - dojo::SerdeLen::::len() - } - - #[external(v0)] - fn schema(self: @ContractState) -> Array<(felt252, felt252, bool)> { - let mut arr = array::ArrayTrait::new(); - array::ArrayTrait::append(ref arr, ('id', 'felt252', true)); - array::ArrayTrait::append(ref arr, ('x', 'felt252', false)); - array::ArrayTrait::append(ref arr, ('y', 'felt252', false)); - arr - } -} - -impl SerdeLenPosition of dojo::SerdeLen { - #[inline(always)] - fn len() -> usize { - dojo::SerdeLen::::len() + dojo::SerdeLen::::len() - } -} - - - -trait PositionTrait { - fn is_zero(self: Position) -> bool; - fn is_equal(self: Position, b: Position) -> bool; -} - - -impl PositionImpl of PositionTrait { - fn is_zero(self: Position) -> bool { - match self.x - self.y { - 0 => bool::True(()), - _ => bool::False(()), - } - } - - fn is_equal(self: Position, b: Position) -> bool { - self.x == b.x && self.y == b.y - } -} - -struct Roles { - role_ids: Array -} - -impl RolesComponent of dojo::traits::Component { - #[inline(always)] - fn name(self: @Roles) -> felt252 { - 'Roles' - } - - #[inline(always)] - fn keys(self: @Roles) -> Span { - let mut serialized = ArrayTrait::new(); - - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn values(self: @Roles) -> Span { - let mut serialized = ArrayTrait::new(); - serde::Serde::serialize(self.role_ids, ref serialized); - array::ArrayTrait::span(@serialized) - } -} - -#[cfg(test)] -impl RolesPrintImpl of debug::PrintTrait { - fn print(self: Roles) { - debug::PrintTrait::print('role_ids'); - debug::PrintTrait::print(self.role_ids); - } -} - -#[starknet::interface] -trait IRoles { - fn name(self: @T) -> felt252; -} - -#[starknet::contract] -mod roles { - use super::Roles; - - #[storage] - struct Storage {} - - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - 'Roles' - } - - #[external(v0)] - fn size(self: @ContractState) -> usize { - dojo::SerdeLen::::len() - } - - #[external(v0)] - fn schema(self: @ContractState) -> Array<(felt252, felt252, bool)> { - let mut arr = array::ArrayTrait::new(); - array::ArrayTrait::append(ref arr, ('role_ids', 'Array', false)); - arr - } -} - - - -impl RolesSerdeLen of dojo::SerdeLen { - #[inline(always)] - fn len() -> usize { - 5 - } -} - - -use starknet::ContractAddress; - -struct Player { - #[key] - game: felt252, - #[key] - player: ContractAddress, - name: felt252, -} - -impl PlayerComponent of dojo::traits::Component { - #[inline(always)] - fn name(self: @Player) -> felt252 { - 'Player' - } - - #[inline(always)] - fn keys(self: @Player) -> Span { - let mut serialized = ArrayTrait::new(); - array::ArrayTrait::append(ref serialized, *self.game); - serde::Serde::serialize(self.player, ref serialized); - - array::ArrayTrait::span(@serialized) - } - - #[inline(always)] - fn values(self: @Player) -> Span { - let mut serialized = ArrayTrait::new(); - array::ArrayTrait::append(ref serialized, *self.name); - - array::ArrayTrait::span(@serialized) - } -} - -#[cfg(test)] -impl PlayerPrintImpl of debug::PrintTrait { - fn print(self: Player) { - debug::PrintTrait::print('game'); - debug::PrintTrait::print(self.game); - debug::PrintTrait::print('player'); - debug::PrintTrait::print(self.player); - debug::PrintTrait::print('name'); - debug::PrintTrait::print(self.name); - } -} - -#[starknet::interface] -trait IPlayer { - fn name(self: @T) -> felt252; -} - -#[starknet::contract] -mod player { - use super::Player; - - #[storage] - struct Storage {} - - #[external(v0)] - fn name(self: @ContractState) -> felt252 { - 'Player' - } - - #[external(v0)] - fn size(self: @ContractState) -> usize { - dojo::SerdeLen::::len() - } - - #[external(v0)] - fn schema(self: @ContractState) -> Array<(felt252, felt252, bool)> { - let mut arr = array::ArrayTrait::new(); - array::ArrayTrait::append(ref arr, ('game', 'felt252', true)); - array::ArrayTrait::append(ref arr, ('player', 'ContractAddress', true)); - array::ArrayTrait::append(ref arr, ('name', 'felt252', false)); - arr - } -} - -impl SerdeLenPlayer of dojo::SerdeLen { - #[inline(always)] - fn len() -> usize { - dojo::SerdeLen::::len() - } -} - -//! > expected_diagnostics -error: Component must define atleast one #[key] attribute - --> dummy_file.cairo:31:8 -struct Roles { - ^***^ diff --git a/crates/dojo-lang/src/plugin_test_data/inline_macros b/crates/dojo-lang/src/plugin_test_data/inline_macros deleted file mode 100644 index 4d69c14b2e..0000000000 --- a/crates/dojo-lang/src/plugin_test_data/inline_macros +++ /dev/null @@ -1,207 +0,0 @@ -//! > Test set! macro. - -//! > test_runner_name -test_expand_plugin - -//! > cairo_code -struct Player { - #[key] - key: felt252, - name: felt252 -} - -struct Position { - #[key] - key: felt252, - x: felt252, - y: felt252 -} - -const world: felt252 = 0xbeef; -const player: Player = Player { key: 'key', name: 'name' }; - -fn foo() { - // Should not emit diagnostics for unknown macros - let arr = array![1, 2, 3]; - set!(world, ( - player, - Position { key: 'key', x: 0, y: 0 }, - )); - - set!(); - set!(world, ()); -} - -//! > generated_cairo_code -struct Player { - #[key] - key: felt252, - name: felt252 -} - - -struct Position { - #[key] - key: felt252, - x: felt252, - y: felt252 -} - - -const world: felt252 = 0xbeef; - -const player: Player = Player { key: 'key', name: 'name' }; - -fn foo() { - // Should not emit diagnostics for unknown macros - let arr = array![1, 2, 3]; - { - let __set_macro_value__ = player; - world - .set_entity( - dojo::traits::Component::name(@__set_macro_value__), - dojo::traits::Component::keys(@__set_macro_value__), - 0_u8, - dojo::traits::Component::values(@__set_macro_value__) - ); - let __set_macro_value__ = Position { key: 'key', x: 0, y: 0 }; - world - .set_entity( - dojo::traits::Component::name(@__set_macro_value__), - dojo::traits::Component::keys(@__set_macro_value__), - 0_u8, - dojo::traits::Component::values(@__set_macro_value__) - ); - }; -; ; } - -//! > expected_diagnostics -error: Invalid arguments. Expected "(world, (components,))" - --> dummy_file.cairo:25:10 - set!(); - ^ - -error: Invalid arguments: No components provided. - --> dummy_file.cairo:26:10 - set!(world, ()); - ^*******^ - -//! > ========================================================================== - -//! > Test get! macro. - -//! > test_runner_name -test_expand_plugin - -//! > cairo_code -fn foo() { - let (position, moves) = get!(world, 0x420, (Position, Moves)); - let position = get!(world, (0x420, 0x1337), Position); - - let id = (0x420, 0x1337); - let position = get!(world, id, Position); -} - -//! > generated_cairo_code -fn foo() { - let (position, moves) = { - let mut __get_macro_keys__ = array::ArrayTrait::new(); - serde::Serde::serialize(@(0x420), ref __get_macro_keys__); - let __get_macro_keys__ = array::ArrayTrait::span(@__get_macro_keys__); - let __Position_values__ = world - .entity('Position', __get_macro_keys__, 0_u8, dojo::SerdeLen::::len()); - let mut __Position_component__ = array::ArrayTrait::new(); - array::serialize_array_helper(__get_macro_keys__, ref __Position_component__); - array::serialize_array_helper(__Position_values__, ref __Position_component__); - let mut __Position_component_span__ = array::ArrayTrait::span(@__Position_component__); - let __Position = option::OptionTrait::expect( - serde::Serde::::deserialize(ref __Position_component_span__), - 'Position failed to deserialize' - ); - let __Moves_values__ = world - .entity('Moves', __get_macro_keys__, 0_u8, dojo::SerdeLen::::len()); - let mut __Moves_component__ = array::ArrayTrait::new(); - array::serialize_array_helper(__get_macro_keys__, ref __Moves_component__); - array::serialize_array_helper(__Moves_values__, ref __Moves_component__); - let mut __Moves_component_span__ = array::ArrayTrait::span(@__Moves_component__); - let __Moves = option::OptionTrait::expect( - serde::Serde::::deserialize(ref __Moves_component_span__), - 'Moves failed to deserialize' - ); - (__Position, __Moves) - }; - let position = { - let mut __get_macro_keys__ = array::ArrayTrait::new(); - serde::Serde::serialize(@(0x420, 0x1337), ref __get_macro_keys__); - let __get_macro_keys__ = array::ArrayTrait::span(@__get_macro_keys__); - let __Position_values__ = world - .entity('Position', __get_macro_keys__, 0_u8, dojo::SerdeLen::::len()); - let mut __Position_component__ = array::ArrayTrait::new(); - array::serialize_array_helper(__get_macro_keys__, ref __Position_component__); - array::serialize_array_helper(__Position_values__, ref __Position_component__); - let mut __Position_component_span__ = array::ArrayTrait::span(@__Position_component__); - let __Position = option::OptionTrait::expect( - serde::Serde::::deserialize(ref __Position_component_span__), - 'Position failed to deserialize' - ); - (__Position) - }; - - let id = (0x420, 0x1337); - let position = { - let mut __get_macro_keys__ = array::ArrayTrait::new(); - serde::Serde::serialize(@id, ref __get_macro_keys__); - let __get_macro_keys__ = array::ArrayTrait::span(@__get_macro_keys__); - let __Position_values__ = world - .entity('Position', __get_macro_keys__, 0_u8, dojo::SerdeLen::::len()); - let mut __Position_component__ = array::ArrayTrait::new(); - array::serialize_array_helper(__get_macro_keys__, ref __Position_component__); - array::serialize_array_helper(__Position_values__, ref __Position_component__); - let mut __Position_component_span__ = array::ArrayTrait::span(@__Position_component__); - let __Position = option::OptionTrait::expect( - serde::Serde::::deserialize(ref __Position_component_span__), - 'Position failed to deserialize' - ); - (__Position) - }; -} - -//! > expected_diagnostics - -//! > ========================================================================== - -//! > Test emit! macro. - -//! > test_runner_name -test_expand_plugin - -//! > cairo_code -fn foo() { - // A comment should not affect macro name - emit!(world, Struct { - x: 10, - }); - - let id = (0x420, 0x1337); - emit!(world, id); -} - -//! > generated_cairo_code -fn foo() { - { - let mut keys = Default::::default(); - let mut data = Default::::default(); - starknet::Event::append_keys_and_data(@Struct { x: 10, }, ref keys, ref data); - world.emit(keys, data.span()); - }; - - let id = (0x420, 0x1337); - { - let mut keys = Default::::default(); - let mut data = Default::::default(); - starknet::Event::append_keys_and_data(@id, ref keys, ref data); - world.emit(keys, data.span()); - }; -} - -//! > expected_diagnostics diff --git a/crates/dojo-lang/src/plugin_test_data/introspect b/crates/dojo-lang/src/plugin_test_data/introspect new file mode 100644 index 0000000000..be0d55726e --- /dev/null +++ b/crates/dojo-lang/src/plugin_test_data/introspect @@ -0,0 +1,255 @@ +//! > Test expansion of the derive(Introspect). + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +use serde::Serde; + +#[derive(Copy, Drop, Serde, Print, Introspect)] +struct Vec2 { + x: u32, + y: u32 +} + +#[derive(Model, Copy, Drop, Print, Introspect)] +struct Position { + #[key] + player: ContractAddress, + vec: Vec2, +} + +//! > generated_cairo_code +use serde::Serde; + + +#[derive(Copy, Drop, Serde, Print, Introspect)] +struct Vec2 { + x: u32, + y: u32 +} + +#[cfg(test)] +impl Vec2PrintImpl of debug::PrintTrait { + fn print(self: Vec2) { + debug::PrintTrait::print('x'); + debug::PrintTrait::print(self.x); + debug::PrintTrait::print('y'); + debug::PrintTrait::print(self.y); + } +} +impl Vec2SchemaIntrospection of dojo::database::schema::SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + dojo::database::schema::SchemaIntrospection::::size() + + dojo::database::schema::SchemaIntrospection::::size() + } + + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + } + + #[inline(always)] + fn ty() -> dojo::database::schema::Ty { + dojo::database::schema::Ty::Struct( + dojo::database::schema::Struct { + name: 'Vec2', + attrs: array![].span(), + children: array![ + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'x', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array![].span() + } + ), + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'y', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array![].span() + } + ) + ] + .span() + } + ) + } +} + + + +#[derive(Model, Copy, Drop, Print, Introspect)] +struct Position { + #[key] + player: ContractAddress, + vec: Vec2, +} +impl PositionModel of dojo::model::Model { + #[inline(always)] + fn name(self: @Position) -> felt252 { + 'Position' + } + + #[inline(always)] + fn keys(self: @Position) -> Span { + let mut serialized = ArrayTrait::new(); + serde::Serde::serialize(self.player, ref serialized); + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn values(self: @Position) -> Span { + let mut serialized = ArrayTrait::new(); + serde::Serde::serialize(self.vec, ref serialized); + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn layout(self: @Position) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[inline(always)] + fn packed_size(self: @Position) -> usize { + let mut layout = self.layout(); + dojo::packing::calculate_packed_size(ref layout) + } +} + + +impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + dojo::database::schema::SchemaIntrospection::::size() + } + + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + } + + #[inline(always)] + fn ty() -> dojo::database::schema::Ty { + dojo::database::schema::Ty::Struct( + dojo::database::schema::Struct { + name: 'Position', + attrs: array![].span(), + children: array![ + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'player', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array!['key'].span() + } + ), + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'vec', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array![].span() + } + ) + ] + .span() + } + ) + } +} + + +#[starknet::interface] +trait IPosition { + fn name(self: @T) -> felt252; +} + +#[starknet::contract] +mod position { + use super::Position; + + #[storage] + struct Storage {} + + #[external(v0)] + fn name(self: @ContractState) -> felt252 { + 'Position' + } + + #[external(v0)] + fn unpacked_size(self: @ContractState) -> usize { + dojo::database::schema::SchemaIntrospection::::size() + } + + #[external(v0)] + fn packed_size(self: @ContractState) -> usize { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + dojo::packing::calculate_packed_size(ref layout_span) + } + + #[external(v0)] + fn layout(self: @ContractState) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[external(v0)] + fn schema(self: @ContractState) -> dojo::database::schema::Ty { + dojo::database::schema::SchemaIntrospection::::ty() + } +} +#[cfg(test)] +impl PositionPrintImpl of debug::PrintTrait { + fn print(self: Position) { + debug::PrintTrait::print('player'); + debug::PrintTrait::print(self.player); + debug::PrintTrait::print('vec'); + debug::PrintTrait::print(self.vec); + } +} +impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + dojo::database::schema::SchemaIntrospection::::size() + } + + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + } + + #[inline(always)] + fn ty() -> dojo::database::schema::Ty { + dojo::database::schema::Ty::Struct( + dojo::database::schema::Struct { + name: 'Position', + attrs: array![].span(), + children: array![ + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'player', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array!['key'].span() + } + ), + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'vec', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array![].span() + } + ) + ] + .span() + } + ) + } +} + +//! > expected_diagnostics diff --git a/crates/dojo-lang/src/plugin_test_data/model b/crates/dojo-lang/src/plugin_test_data/model new file mode 100644 index 0000000000..a60d0c5f57 --- /dev/null +++ b/crates/dojo-lang/src/plugin_test_data/model @@ -0,0 +1,474 @@ +//! > Test expansion of the derive(Model). + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +use serde::Serde; + +#[derive(Model, Copy, Drop, Serde)] +struct Position { + #[key] + id: felt252, + x: felt252, + y: felt252 +} + +trait PositionTrait { + fn is_zero(self: Position) -> bool; + fn is_equal(self: Position, b: Position) -> bool; +} + +impl PositionImpl of PositionTrait { + fn is_zero(self: Position) -> bool { + match self.x - self.y { + 0 => bool::True(()), + _ => bool::False(()), + } + } + + fn is_equal(self: Position, b: Position) -> bool { + self.x == b.x && self.y == b.y + } +} + +#[derive(Model, Serde)] +struct Roles { + role_ids: Array +} + +use starknet::ContractAddress; + +#[derive(Model, Copy, Drop, Serde)] +struct Player { + #[key] + game: felt252, + #[key] + player: ContractAddress, + + name: felt252, +} + +//! > generated_cairo_code +use serde::Serde; + + +#[derive(Model, Copy, Drop, Serde)] +struct Position { + #[key] + id: felt252, + x: felt252, + y: felt252 +} + +impl PositionModel of dojo::model::Model { + #[inline(always)] + fn name(self: @Position) -> felt252 { + 'Position' + } + + #[inline(always)] + fn keys(self: @Position) -> Span { + let mut serialized = ArrayTrait::new(); + array::ArrayTrait::append(ref serialized, *self.id); + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn values(self: @Position) -> Span { + let mut serialized = ArrayTrait::new(); + array::ArrayTrait::append(ref serialized, *self.x); + array::ArrayTrait::append(ref serialized, *self.y); + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn layout(self: @Position) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[inline(always)] + fn packed_size(self: @Position) -> usize { + let mut layout = self.layout(); + dojo::packing::calculate_packed_size(ref layout) + } +} + + +impl PositionSchemaIntrospection of dojo::database::schema::SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + dojo::database::schema::SchemaIntrospection::::size() + + dojo::database::schema::SchemaIntrospection::::size() + } + + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + } + + #[inline(always)] + fn ty() -> dojo::database::schema::Ty { + dojo::database::schema::Ty::Struct( + dojo::database::schema::Struct { + name: 'Position', + attrs: array![].span(), + children: array![ + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'id', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array!['key'].span() + } + ), + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'x', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array![].span() + } + ), + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'y', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array![].span() + } + ) + ] + .span() + } + ) + } +} + + +#[starknet::interface] +trait IPosition { + fn name(self: @T) -> felt252; +} + +#[starknet::contract] +mod position { + use super::Position; + + #[storage] + struct Storage {} + + #[external(v0)] + fn name(self: @ContractState) -> felt252 { + 'Position' + } + + #[external(v0)] + fn unpacked_size(self: @ContractState) -> usize { + dojo::database::schema::SchemaIntrospection::::size() + } + + #[external(v0)] + fn packed_size(self: @ContractState) -> usize { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + dojo::packing::calculate_packed_size(ref layout_span) + } + + #[external(v0)] + fn layout(self: @ContractState) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[external(v0)] + fn schema(self: @ContractState) -> dojo::database::schema::Ty { + dojo::database::schema::SchemaIntrospection::::ty() + } +} + + + +trait PositionTrait { + fn is_zero(self: Position) -> bool; + fn is_equal(self: Position, b: Position) -> bool; +} + + +impl PositionImpl of PositionTrait { + fn is_zero(self: Position) -> bool { + match self.x - self.y { + 0 => bool::True(()), + _ => bool::False(()), + } + } + + fn is_equal(self: Position, b: Position) -> bool { + self.x == b.x && self.y == b.y + } +} + + +#[derive(Model, Serde)] +struct Roles { + role_ids: Array +} + +impl RolesModel of dojo::model::Model { + #[inline(always)] + fn name(self: @Roles) -> felt252 { + 'Roles' + } + + #[inline(always)] + fn keys(self: @Roles) -> Span { + let mut serialized = ArrayTrait::new(); + + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn values(self: @Roles) -> Span { + let mut serialized = ArrayTrait::new(); + serde::Serde::serialize(self.role_ids, ref serialized); + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn layout(self: @Roles) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[inline(always)] + fn packed_size(self: @Roles) -> usize { + let mut layout = self.layout(); + dojo::packing::calculate_packed_size(ref layout) + } +} + + +impl RolesSchemaIntrospection of dojo::database::schema::SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + dojo::database::schema::SchemaIntrospection::>::size() + } + + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::schema::SchemaIntrospection::>::layout(ref layout); + } + + #[inline(always)] + fn ty() -> dojo::database::schema::Ty { + dojo::database::schema::Ty::Struct( + dojo::database::schema::Struct { + name: 'Roles', + attrs: array![].span(), + children: array![ + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'role_ids', + ty: dojo::database::schema::SchemaIntrospection::>::ty(), + attrs: array![].span() + } + ) + ] + .span() + } + ) + } +} + + +#[starknet::interface] +trait IRoles { + fn name(self: @T) -> felt252; +} + +#[starknet::contract] +mod roles { + use super::Roles; + + #[storage] + struct Storage {} + + #[external(v0)] + fn name(self: @ContractState) -> felt252 { + 'Roles' + } + + #[external(v0)] + fn unpacked_size(self: @ContractState) -> usize { + dojo::database::schema::SchemaIntrospection::::size() + } + + #[external(v0)] + fn packed_size(self: @ContractState) -> usize { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + dojo::packing::calculate_packed_size(ref layout_span) + } + + #[external(v0)] + fn layout(self: @ContractState) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[external(v0)] + fn schema(self: @ContractState) -> dojo::database::schema::Ty { + dojo::database::schema::SchemaIntrospection::::ty() + } +} + + + +use starknet::ContractAddress; + + +#[derive(Model, Copy, Drop, Serde)] +struct Player { + #[key] + game: felt252, + #[key] + player: ContractAddress, + + name: felt252, +} +impl PlayerModel of dojo::model::Model { + #[inline(always)] + fn name(self: @Player) -> felt252 { + 'Player' + } + + #[inline(always)] + fn keys(self: @Player) -> Span { + let mut serialized = ArrayTrait::new(); + array::ArrayTrait::append(ref serialized, *self.game); + serde::Serde::serialize(self.player, ref serialized); + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn values(self: @Player) -> Span { + let mut serialized = ArrayTrait::new(); + array::ArrayTrait::append(ref serialized, *self.name); + array::ArrayTrait::span(@serialized) + } + + #[inline(always)] + fn layout(self: @Player) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[inline(always)] + fn packed_size(self: @Player) -> usize { + let mut layout = self.layout(); + dojo::packing::calculate_packed_size(ref layout) + } +} + + +impl PlayerSchemaIntrospection of dojo::database::schema::SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + dojo::database::schema::SchemaIntrospection::::size() + } + + #[inline(always)] + fn layout(ref layout: Array) { + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + } + + #[inline(always)] + fn ty() -> dojo::database::schema::Ty { + dojo::database::schema::Ty::Struct( + dojo::database::schema::Struct { + name: 'Player', + attrs: array![].span(), + children: array![ + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'game', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array!['key'].span() + } + ), + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'player', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array!['key'].span() + } + ), + dojo::database::schema::serialize_member( + @dojo::database::schema::Member { + name: 'name', + ty: dojo::database::schema::SchemaIntrospection::::ty(), + attrs: array![].span() + } + ) + ] + .span() + } + ) + } +} + + +#[starknet::interface] +trait IPlayer { + fn name(self: @T) -> felt252; +} + +#[starknet::contract] +mod player { + use super::Player; + + #[storage] + struct Storage {} + + #[external(v0)] + fn name(self: @ContractState) -> felt252 { + 'Player' + } + + #[external(v0)] + fn unpacked_size(self: @ContractState) -> usize { + dojo::database::schema::SchemaIntrospection::::size() + } + + #[external(v0)] + fn packed_size(self: @ContractState) -> usize { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + let mut layout_span = layout.span(); + dojo::packing::calculate_packed_size(ref layout_span) + } + + #[external(v0)] + fn layout(self: @ContractState) -> Span { + let mut layout = ArrayTrait::new(); + dojo::database::schema::SchemaIntrospection::::layout(ref layout); + array::ArrayTrait::span(@layout) + } + + #[external(v0)] + fn schema(self: @ContractState) -> dojo::database::schema::Ty { + dojo::database::schema::SchemaIntrospection::::ty() + } +} + +//! > expected_diagnostics +error: Model must define atleast one #[key] attribute + --> dummy_file.cairo:30:8 +struct Roles { + ^***^ diff --git a/crates/dojo-lang/src/plugin_test_data/print b/crates/dojo-lang/src/plugin_test_data/print new file mode 100644 index 0000000000..cf138de450 --- /dev/null +++ b/crates/dojo-lang/src/plugin_test_data/print @@ -0,0 +1,99 @@ +//! > Test expansion of the derive(Print). + +//! > test_runner_name +test_expand_plugin + +//! > cairo_code +use serde::Serde; + +#[derive(Print, Copy, Drop, Serde)] +struct Position { + #[key] + id: felt252, + + x: felt252, + y: felt252 +} + +#[derive(Print, Serde)] +struct Roles { + role_ids: Array +} + +use starknet::ContractAddress; + +#[derive(Print, Copy, Drop, Serde)] +struct Player { + #[key] + game: felt252, + #[key] + player: ContractAddress, + + name: felt252, +} + +//! > generated_cairo_code +use serde::Serde; + + +#[derive(Print, Copy, Drop, Serde)] +struct Position { + #[key] + id: felt252, + + x: felt252, + y: felt252 +} + +#[cfg(test)] +impl PositionPrintImpl of debug::PrintTrait { + fn print(self: Position) { + debug::PrintTrait::print('id'); + debug::PrintTrait::print(self.id); + debug::PrintTrait::print('x'); + debug::PrintTrait::print(self.x); + debug::PrintTrait::print('y'); + debug::PrintTrait::print(self.y); + } +} + + +#[derive(Print, Serde)] +struct Roles { + role_ids: Array +} + +#[cfg(test)] +impl RolesPrintImpl of debug::PrintTrait { + fn print(self: Roles) { + debug::PrintTrait::print('role_ids'); + debug::PrintTrait::print(self.role_ids); + } +} + + +use starknet::ContractAddress; + + +#[derive(Print, Copy, Drop, Serde)] +struct Player { + #[key] + game: felt252, + #[key] + player: ContractAddress, + + name: felt252, +} +#[cfg(test)] +impl PlayerPrintImpl of debug::PrintTrait { + fn print(self: Player) { + debug::PrintTrait::print('game'); + debug::PrintTrait::print(self.game); + debug::PrintTrait::print('player'); + debug::PrintTrait::print(self.player); + debug::PrintTrait::print('name'); + debug::PrintTrait::print(self.name); + } +} + +//! > expected_diagnostics diff --git a/crates/dojo-lang/src/plugin_test_data/system b/crates/dojo-lang/src/plugin_test_data/system index 18cbfa3378..f6459214ab 100644 --- a/crates/dojo-lang/src/plugin_test_data/system +++ b/crates/dojo-lang/src/plugin_test_data/system @@ -1,4 +1,4 @@ -//! > Test expansion of the component contract. +//! > Test expansion of the #[system]. //! > test_runner_name test_expand_plugin @@ -50,7 +50,7 @@ mod spawn { use dojo::world::Context; #[external(v0)] - fn execute(self: @ContractState, name: felt252, ctx: Context) { + fn execute(self: @ContractState, ctx: Context, name: felt252) { return (); } } @@ -72,7 +72,7 @@ mod proxy { #[external(v0)] - fn execute(self: @ContractState, value: felt252, _ctx: dojo::world::Context) -> felt252 { + fn execute(self: @ContractState, value: felt252) -> felt252 { value } } @@ -96,7 +96,7 @@ mod ctxnamed { use dojo::world::Context; #[external(v0)] - fn execute(self: @ContractState, name: felt252, ctx2: Context) { + fn execute(self: @ContractState, ctx2: Context, name: felt252) { return (); } } diff --git a/crates/dojo-lang/src/print.rs b/crates/dojo-lang/src/print.rs new file mode 100644 index 0000000000..0506fa368c --- /dev/null +++ b/crates/dojo-lang/src/print.rs @@ -0,0 +1,42 @@ +use cairo_lang_defs::patcher::RewriteNode; +use cairo_lang_syntax::node::ast::ItemStruct; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode}; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; + +/// Derives PrintTrait for a struct. +/// Parameters: +/// * db: The semantic database. +/// * struct_ast: The AST of the model struct. +/// Returns: +/// * A RewriteNode containing the generated code. +pub fn derive_print(db: &dyn SyntaxGroup, struct_ast: ItemStruct) -> RewriteNode { + let prints: Vec<_> = struct_ast + .members(db) + .elements(db) + .iter() + .map(|m| { + format!( + "debug::PrintTrait::print('{}'); debug::PrintTrait::print(self.{});", + m.name(db).text(db).to_string(), + m.name(db).text(db).to_string() + ) + }) + .collect(); + + RewriteNode::interpolate_patched( + "#[cfg(test)] + impl $type_name$PrintImpl of debug::PrintTrait<$type_name$> { + fn print(self: $type_name$) { + $print$ + } + }", + UnorderedHashMap::from([ + ( + "type_name".to_string(), + RewriteNode::new_trimmed(struct_ast.name(db).as_syntax_node()), + ), + ("print".to_string(), RewriteNode::Text(prints.join("\n"))), + ]), + ) +} diff --git a/crates/dojo-lang/src/serde.rs b/crates/dojo-lang/src/serde.rs deleted file mode 100644 index 8b4084f14f..0000000000 --- a/crates/dojo-lang/src/serde.rs +++ /dev/null @@ -1,50 +0,0 @@ -use cairo_lang_semantic::patcher::RewriteNode; -use cairo_lang_syntax::node::ast::ItemStruct; -use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::helpers::QueryAttrs; -use cairo_lang_syntax::node::TypedSyntaxNode; -use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; -use itertools::Itertools; - -/// A handler for Dojo code derives SerdeLen for a struct -/// Parameters: -/// * db: The semantic database. -/// * struct_ast: The AST of the struct. -/// Returns: -/// * A RewriteNode containing the generated code. -pub fn handle_serde_len_struct(db: &dyn SyntaxGroup, struct_ast: ItemStruct) -> RewriteNode { - RewriteNode::interpolate_patched( - " - impl SerdeLen$name$ of dojo::SerdeLen<$type$> { - #[inline(always)] - fn len() -> usize { - $len$ - } - } - ", - UnorderedHashMap::from([ - ("name".to_string(), RewriteNode::new_trimmed(struct_ast.name(db).as_syntax_node())), - ("type".to_string(), RewriteNode::new_trimmed(struct_ast.name(db).as_syntax_node())), - ( - "len".to_string(), - RewriteNode::Text( - struct_ast - .members(db) - .elements(db) - .iter() - .filter_map(|member| { - if member.has_attr(db, "key") { - return None; - } - - Some(format!( - "dojo::SerdeLen::<{}>::len()", - member.type_clause(db).ty(db).as_syntax_node().get_text(db), - )) - }) - .join(" + "), - ), - ), - ]), - ) -} diff --git a/crates/dojo-lang/src/system/mod.rs b/crates/dojo-lang/src/system.rs similarity index 74% rename from crates/dojo-lang/src/system/mod.rs rename to crates/dojo-lang/src/system.rs index dd3cd26551..dd22a75068 100644 --- a/crates/dojo-lang/src/system/mod.rs +++ b/crates/dojo-lang/src/system.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; +use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode}; use cairo_lang_defs::plugin::{ DynGeneratedFileAuxData, PluginDiagnostic, PluginGeneratedFile, PluginResult, }; -use cairo_lang_semantic::patcher::{PatchBuilder, RewriteNode}; -use cairo_lang_semantic::plugin::DynPluginAuxData; +// use cairo_lang_syntax::node::ast::{MaybeModuleBody, Param}; +use cairo_lang_syntax::node::ast::MaybeModuleBody; use cairo_lang_syntax::node::ast::OptionReturnTypeClause::ReturnTypeClause; -use cairo_lang_syntax::node::ast::{MaybeModuleBody, Param}; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode}; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; @@ -70,14 +70,14 @@ impl System { code: Some(PluginGeneratedFile { name: name.clone(), content: builder.code, - aux_data: DynGeneratedFileAuxData::new(DynPluginAuxData::new(DojoAuxData { - patches: builder.patches, - components: vec![], + aux_data: Some(DynGeneratedFileAuxData::new(DojoAuxData { + models: vec![], systems: vec![SystemAuxData { name, dependencies: system.dependencies.values().cloned().collect(), }], })), + diagnostics_mappings: builder.diagnostics_mappings, }), diagnostics: system.diagnostics, remove_original_item: true, @@ -97,26 +97,25 @@ impl System { let signature = function_ast.declaration(db).signature(db); let parameters = signature.parameters(db); - let mut elements = parameters.elements(db); - - let mut context = "_ctx: dojo::world::Context".to_string(); - if let Some(first) = elements.first() { - // If context is first, move it to last. - if is_context(db, first) { - let ctx = elements.remove(0); - context = ctx.as_syntax_node().get_text(db); - } - } else if let Some(param) = elements.iter().find(|p| is_context(db, p)) { - // Context not the first element, but exists. - self.diagnostics.push(PluginDiagnostic { - message: "Context must be first parameter when provided".into(), - stable_ptr: param.stable_ptr().untyped(), - }); - } - - let mut params = - elements.iter().map(|e| e.as_syntax_node().get_text(db)).collect::>(); - params.push(context); + let elements = parameters.elements(db); + + // let mut context = "_ctx: dojo::world::Context".to_string(); + // if let Some(first) = elements.first() { + // // If context is first, move it to last. + // if is_context(db, first) { + // let ctx = elements.remove(0); + // context = ctx.as_syntax_node().get_text(db); + // } + // } else if let Some(param) = elements.iter().find(|p| is_context(db, p)) { + // // Context not the first element, but exists. + // self.diagnostics.push(PluginDiagnostic { + // message: "Context must be first parameter when provided".into(), + // stable_ptr: param.stable_ptr().untyped(), + // }); + // } + + let params = elements.iter().map(|e| e.as_syntax_node().get_text(db)).collect::>(); + // params.push(context); let params = params.join(", "); let ret_clause = if let ReturnTypeClause(clause) = signature.ret_ty(db) { @@ -144,6 +143,6 @@ impl System { } } -fn is_context(db: &dyn SyntaxGroup, param: &Param) -> bool { - param.type_clause(db).ty(db).as_syntax_node().get_text(db) == "Context" -} +// fn is_context(db: &dyn SyntaxGroup, param: &Param) -> bool { +// param.type_clause(db).ty(db).as_syntax_node().get_text(db) == "Context" +// } diff --git a/crates/dojo-language-server/src/bin/language_server.rs b/crates/dojo-language-server/src/bin/language_server.rs index 68f0ec0aa1..dd95b30a4d 100644 --- a/crates/dojo-language-server/src/bin/language_server.rs +++ b/crates/dojo-language-server/src/bin/language_server.rs @@ -29,8 +29,8 @@ async fn main() { let db = RootDatabase::builder() .with_cfg(CfgSet::from_iter([Cfg::name("test")])) - .with_semantic_plugin(Arc::new(DojoPlugin)) - .with_semantic_plugin(Arc::new(StarkNetPlugin::default())) + .with_macro_plugin(Arc::new(DojoPlugin)) + .with_macro_plugin(Arc::new(StarkNetPlugin::default())) .build() .unwrap_or_else(|error| { panic!("Problem creating language database: {error:?}"); diff --git a/crates/dojo-test-utils/Cargo.toml b/crates/dojo-test-utils/Cargo.toml index c84848d640..8e2cd1ff51 100644 --- a/crates/dojo-test-utils/Cargo.toml +++ b/crates/dojo-test-utils/Cargo.toml @@ -15,9 +15,11 @@ cairo-lang-project.workspace = true cairo-lang-starknet.workspace = true camino.workspace = true dojo-lang = { path = "../dojo-lang" } +dojo-world = { path = "../dojo-world" } jsonrpsee = { version = "0.16.2", features = [ "server" ] } katana-core = { path = "../katana/core" } katana-rpc = { path = "../katana/rpc" } +scarb-ui.workspace = true scarb.workspace = true serde.workspace = true serde_json.workspace = true @@ -34,6 +36,7 @@ url = "2.2.2" assert_fs = "1.0.9" camino.workspace = true dojo-lang = { path = "../dojo-lang" } +scarb-ui.workspace = true scarb.workspace = true [features] diff --git a/crates/dojo-test-utils/build.rs b/crates/dojo-test-utils/build.rs index 98c824fe37..cfa8f34a9a 100644 --- a/crates/dojo-test-utils/build.rs +++ b/crates/dojo-test-utils/build.rs @@ -8,7 +8,7 @@ fn main() { use scarb::compiler::CompilerRepository; use scarb::core::Config; use scarb::ops; - use scarb::ui::Verbosity; + use scarb_ui::Verbosity; let target_path = Utf8PathBuf::from_path_buf("../../examples/ecs/target".into()).unwrap(); if target_path.exists() { diff --git a/crates/dojo-test-utils/src/compiler.rs b/crates/dojo-test-utils/src/compiler.rs index e801e7469e..0b79059432 100644 --- a/crates/dojo-test-utils/src/compiler.rs +++ b/crates/dojo-test-utils/src/compiler.rs @@ -6,7 +6,7 @@ use dojo_lang::compiler::DojoCompiler; use dojo_lang::plugin::CairoPluginRepository; use scarb::compiler::CompilerRepository; use scarb::core::Config; -use scarb::ui::Verbosity; +use scarb_ui::Verbosity; pub fn build_test_config(path: &str) -> anyhow::Result { let mut compilers = CompilerRepository::empty(); diff --git a/crates/dojo-test-utils/src/lib.rs b/crates/dojo-test-utils/src/lib.rs index 36a1b63872..f8f7c63e82 100644 --- a/crates/dojo-test-utils/src/lib.rs +++ b/crates/dojo-test-utils/src/lib.rs @@ -1,3 +1,6 @@ pub mod compiler; +pub mod migration; pub mod rpc; pub mod sequencer; + +pub use dojo_world::utils::{TransactionWaiter, TransactionWaitingError}; diff --git a/crates/dojo-test-utils/src/migration.rs b/crates/dojo-test-utils/src/migration.rs new file mode 100644 index 0000000000..4e5c001183 --- /dev/null +++ b/crates/dojo-test-utils/src/migration.rs @@ -0,0 +1,15 @@ +use std::path::PathBuf; + +use anyhow::Result; +use camino::Utf8PathBuf; +use dojo_world::manifest::Manifest; +use dojo_world::migration::strategy::{prepare_for_migration, MigrationStrategy}; +use dojo_world::migration::world::WorldDiff; +use starknet::macros::felt; + +pub fn prepare_migration(path: PathBuf) -> Result { + let target_dir = Utf8PathBuf::from_path_buf(path).unwrap(); + let manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); + let world = WorldDiff::compute(manifest, None); + prepare_for_migration(None, Some(felt!("0x12345")), target_dir, world) +} diff --git a/crates/dojo-test-utils/src/sequencer.rs b/crates/dojo-test-utils/src/sequencer.rs index 76a224bdbd..abd214478c 100644 --- a/crates/dojo-test-utils/src/sequencer.rs +++ b/crates/dojo-test-utils/src/sequencer.rs @@ -6,7 +6,7 @@ use katana_core::sequencer::KatanaSequencer; pub use katana_core::sequencer::SequencerConfig; use katana_rpc::config::ServerConfig; use katana_rpc::{spawn, KatanaApi, NodeHandle, StarknetApi}; -use starknet::accounts::SingleOwnerAccount; +use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; use starknet::core::chain_id; use starknet::core::types::FieldElement; use starknet::providers::jsonrpc::HttpTransport; @@ -29,9 +29,7 @@ pub struct TestSequencer { impl TestSequencer { pub async fn start(config: SequencerConfig, starknet_config: StarknetConfig) -> Self { - let sequencer = Arc::new(KatanaSequencer::new(config, starknet_config)); - - sequencer.start().await; + let sequencer = Arc::new(KatanaSequencer::new(config, starknet_config).await); let starknet_api = StarknetApi::new(sequencer.clone()); let katana_api = KatanaApi::new(sequencer.clone()); @@ -56,6 +54,7 @@ impl TestSequencer { LocalWallet::from_signing_key(SigningKey::from_secret_scalar(self.account.private_key)), self.account.account_address, chain_id::TESTNET, + ExecutionEncoding::Legacy, ) } diff --git a/crates/dojo-types/Cargo.toml b/crates/dojo-types/Cargo.toml index 55983f0290..09d62e8de9 100644 --- a/crates/dojo-types/Cargo.toml +++ b/crates/dojo-types/Cargo.toml @@ -6,5 +6,9 @@ version = "0.2.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +hex = "0.4.3" +strum.workspace = true +strum_macros.workspace = true serde.workspace = true starknet.workspace = true +thiserror.workspace = true diff --git a/crates/dojo-types/src/component.rs b/crates/dojo-types/src/component.rs deleted file mode 100644 index 766f5d2084..0000000000 --- a/crates/dojo-types/src/component.rs +++ /dev/null @@ -1,12 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Represents a component member. -#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct Member { - /// Name of the member. - pub name: String, - /// Type of the member. - #[serde(rename = "type")] - pub ty: String, - pub key: bool, -} diff --git a/crates/dojo-types/src/core.rs b/crates/dojo-types/src/core.rs new file mode 100644 index 0000000000..996e047589 --- /dev/null +++ b/crates/dojo-types/src/core.rs @@ -0,0 +1,79 @@ +use starknet::core::types::FieldElement; +use strum_macros::{AsRefStr, Display, EnumIter, EnumString}; + +#[derive(AsRefStr, Display, EnumIter, EnumString, Debug)] +#[strum(serialize_all = "lowercase")] +pub enum CairoType { + U8, + U16, + U32, + U64, + U128, + U256, + USize, + Bool, + Felt252, + #[strum(serialize = "ClassHash")] + ClassHash, + #[strum(serialize = "ContractAddress")] + ContractAddress, +} + +#[derive(Debug, thiserror::Error)] +pub enum CairoTypeError { + #[error("Value must have at least one FieldElement")] + MissingFieldElement, + #[error("Not enough FieldElements for U256")] + NotEnoughFieldElements, + #[error("Unsupported CairoType for SQL formatting")] + UnsupportedType, +} + +impl CairoType { + pub fn to_sql_type(&self) -> String { + match self { + CairoType::U8 + | CairoType::U16 + | CairoType::U32 + | CairoType::U64 + | CairoType::USize + | CairoType::Bool => "INTEGER".to_string(), + CairoType::U128 + | CairoType::U256 + | CairoType::ContractAddress + | CairoType::ClassHash + | CairoType::Felt252 => "TEXT".to_string(), + } + } + + pub fn format_for_sql(&self, value: Vec<&FieldElement>) -> Result { + if value.is_empty() { + return Err(CairoTypeError::MissingFieldElement); + } + + match self { + CairoType::U8 + | CairoType::U16 + | CairoType::U32 + | CairoType::U64 + | CairoType::USize + | CairoType::Bool => Ok(format!(", '{}'", value[0])), + CairoType::U128 + | CairoType::ContractAddress + | CairoType::ClassHash + | CairoType::Felt252 => Ok(format!(", '{:0>64x}'", value[0])), + CairoType::U256 => { + if value.len() < 2 { + Err(CairoTypeError::NotEnoughFieldElements) + } else { + let mut buffer = [0u8; 32]; + let value0_bytes = value[0].to_bytes_be(); + let value1_bytes = value[1].to_bytes_be(); + buffer[..16].copy_from_slice(&value0_bytes); + buffer[16..].copy_from_slice(&value1_bytes); + Ok(format!(", '{}'", hex::encode(buffer))) + } + } + } + } +} diff --git a/crates/dojo-types/src/event.rs b/crates/dojo-types/src/event.rs index f5b6f8ab23..38319ff684 100644 --- a/crates/dojo-types/src/event.rs +++ b/crates/dojo-types/src/event.rs @@ -7,21 +7,14 @@ pub struct WorldSpawned { pub caller: FieldElement, } -/// The event emitted when a system is registered to a World. +/// The event emitted when a model is registered to a World. #[derive(Clone, Debug)] -pub struct SystemRegistered { +pub struct ModelRegistered { pub name: String, pub class_hash: FieldElement, } -/// The event emitted when a component is registered to a World. -#[derive(Clone, Debug)] -pub struct ComponentRegistered { - pub name: String, - pub class_hash: FieldElement, -} - -/// The event emmitted when a component value of an entity is set. +/// The event emmitted when a model value of an entity is set. #[derive(Clone, Debug)] pub struct StoreSetRecord { pub table_id: FieldElement, @@ -30,7 +23,7 @@ pub struct StoreSetRecord { pub value: Vec, } -/// The event emmitted when a component is deleted from an entity. +/// The event emmitted when a model is deleted from an entity. #[derive(Clone, Debug)] pub struct StoreDelRecord { pub table_id: FieldElement, diff --git a/crates/dojo-types/src/lib.rs b/crates/dojo-types/src/lib.rs index f0ff6617a9..07f1b87f29 100644 --- a/crates/dojo-types/src/lib.rs +++ b/crates/dojo-types/src/lib.rs @@ -1,4 +1,23 @@ -pub mod component; +use std::collections::HashMap; + +use model::ModelMetadata; +use serde::Serialize; +use starknet::core::types::FieldElement; +use system::SystemMetadata; + +pub mod core; pub mod event; +pub mod model; pub mod storage; pub mod system; + +/// Represents the metadata of a World +#[derive(Debug, Clone, Serialize, Default)] +pub struct WorldMetadata { + pub world_address: FieldElement, + pub world_class_hash: FieldElement, + pub executor_address: FieldElement, + pub executor_class_hash: FieldElement, + pub systems: HashMap, + pub components: HashMap, +} diff --git a/crates/dojo-types/src/model.rs b/crates/dojo-types/src/model.rs new file mode 100644 index 0000000000..4cb0ed2f75 --- /dev/null +++ b/crates/dojo-types/src/model.rs @@ -0,0 +1,144 @@ +use serde::{Deserialize, Serialize}; +use starknet::core::types::FieldElement; + +/// Represents a model member. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Member { + pub name: String, + pub ty: Ty, + pub key: bool, +} + +/// Represents a component of an entity +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct EntityModel { + pub model: String, + pub keys: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ModelMetadata { + pub name: String, + pub size: u32, + pub class_hash: FieldElement, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum Ty { + Terminal(String), + Struct(Struct), + Enum(Enum), +} + +impl Ty { + pub fn name(&self) -> String { + match self { + Ty::Terminal(s) => s.clone(), + Ty::Struct(s) => s.name.clone(), + Ty::Enum(e) => e.name.clone(), + } + } + + pub fn flatten(&self) -> Vec { + let mut flattened = Ty::flatten_ty(self.clone()); + flattened.reverse(); + flattened + } + + fn flatten_ty(ty: Ty) -> Vec { + let mut items = vec![]; + match ty { + Ty::Terminal(_) => { + items.push(ty.clone()); + } + Ty::Struct(mut s) => { + for (i, member) in s.children.clone().iter().enumerate() { + match member.ty { + Ty::Struct(_) => { + items.extend(Ty::flatten_ty(member.ty.clone())); + } + Ty::Enum(_) => { + items.extend(Ty::flatten_ty(member.ty.clone())); + } + _ => {} + } + + s.children[i].ty = Ty::Terminal(member.ty.name()); + } + + items.push(Ty::Struct(s)) + } + Ty::Enum(mut e) => { + for (i, ty) in e.values.clone().iter().enumerate() { + match ty { + Ty::Struct(_) => { + items.extend(Ty::flatten_ty(ty.clone())); + } + Ty::Enum(_) => { + items.extend(Ty::flatten_ty(ty.clone())); + } + _ => {} + } + + e.values[i] = Ty::Terminal(ty.name()); + } + + items.push(Ty::Enum(e)) + } + }; + + items + } +} + +impl std::fmt::Display for Ty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut items = self.flatten(); + items.reverse(); + let str = items + .iter() + .map(|ty| match ty { + Ty::Terminal(s) => s.to_string(), + Ty::Struct(s) => { + let mut struct_str = format!("struct {} {{\n", s.name); + for member in &s.children { + struct_str.push_str(&format!("{},\n", format_member(member))); + } + struct_str.push('}'); + struct_str + } + Ty::Enum(e) => { + let mut enum_str = format!("enum {} {{\n", e.name); + for ty in &e.values { + enum_str.push_str(&format!(" {}\n", ty.name())); + } + enum_str.push('}'); + enum_str + } + }) + .collect::>() + .join("\n\n"); + + write!(f, "{}", str) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Struct { + pub name: String, + pub children: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Enum { + pub name: String, + pub values: Vec, +} + +fn format_member(m: &Member) -> String { + if m.key { + format!(" #[key]\n {}: {}", m.name, m.ty.name()) + } else { + format!(" {}: {}", m.name, m.ty.name()) + } +} diff --git a/crates/dojo-types/src/system.rs b/crates/dojo-types/src/system.rs index e4853d0865..b873a3a7f1 100644 --- a/crates/dojo-types/src/system.rs +++ b/crates/dojo-types/src/system.rs @@ -1,10 +1,17 @@ use serde::{Deserialize, Serialize}; +use starknet::core::types::FieldElement; -/// Represents a system's component dependency. +/// Represents a system's model dependency. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Dependency { - /// Name of the component. + /// Name of the model. pub name: String, pub read: bool, pub write: bool, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SystemMetadata { + pub name: String, + pub class_hash: FieldElement, +} diff --git a/crates/dojo-world/src/manifest.rs b/crates/dojo-world/src/manifest.rs index 8ff9892095..ef43909513 100644 --- a/crates/dojo-world/src/manifest.rs +++ b/crates/dojo-world/src/manifest.rs @@ -4,7 +4,6 @@ use std::path::Path; use ::serde::{Deserialize, Serialize}; use anyhow::{anyhow, Result}; use cairo_lang_starknet::abi; -use dojo_types::component::Member; use dojo_types::system::Dependency; use serde_with::serde_as; use smol_str::SmolStr; @@ -39,10 +38,27 @@ pub enum ManifestError { Provider(ProviderError), } -/// Represents a declaration of a component. +/// Represents a model member. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Member { + /// Name of the member. + pub name: String, + /// Type of the member. + #[serde(rename = "type")] + pub ty: String, + pub key: bool, +} + +impl From for Member { + fn from(m: dojo_types::model::Member) -> Self { + Self { name: m.name, ty: m.ty.name(), key: m.key } + } +} + +/// Represents a declaration of a model. #[serde_as] #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct Component { +pub struct Model { pub name: String, pub members: Vec, #[serde_as(as = "UfeHex")] @@ -96,7 +112,7 @@ pub struct Manifest { pub executor: Contract, pub systems: Vec, pub contracts: Vec, - pub components: Vec, + pub models: Vec, } impl Manifest { @@ -151,27 +167,27 @@ impl Manifest { })?; let mut systems = vec![]; - let mut components = vec![]; + let mut models = vec![]; if let Some(match_manifest) = match_manifest { - for component in match_manifest.components { + for model in match_manifest.models { let result = provider .call( FunctionCall { contract_address: world_address, calldata: vec![ - cairo_short_string_to_felt(&component.name) + cairo_short_string_to_felt(&model.name) .map_err(ManifestError::InvalidNameError)?, ], - entry_point_selector: get_selector_from_name("component").unwrap(), + entry_point_selector: get_selector_from_name("model").unwrap(), }, BlockId::Tag(BlockTag::Pending), ) .await .map_err(ManifestError::Provider)?; - components.push(Component { - name: component.name.clone(), + models.push(Model { + name: model.name.clone(), class_hash: result[0], ..Default::default() }); @@ -207,7 +223,7 @@ impl Manifest { Ok(Manifest { systems, - components, + models, contracts: vec![], world: Contract { name: WORLD_CONTRACT_NAME.into(), diff --git a/crates/dojo-world/src/metadata.rs b/crates/dojo-world/src/metadata.rs index 70a8fc4d9e..584b84405e 100644 --- a/crates/dojo-world/src/metadata.rs +++ b/crates/dojo-world/src/metadata.rs @@ -77,8 +77,8 @@ mod test { r#" [env] rpc_url = "http://localhost:5050/" -account_address = "0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0" -private_key = "0x0300001800000000300000180000000000030000000000003006001800006600" +account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" +private_key = "0x1800000000300000180000000000030000000000003006001800006600" keystore_path = "test/" keystore_password = "dojo" world_address = "0x0248cacaeac64c45be0c19ee8727e0bb86623ca7fa3f0d431a6c55e200697e5a" @@ -92,11 +92,11 @@ world_address = "0x0248cacaeac64c45be0c19ee8727e0bb86623ca7fa3f0d431a6c55e200697 assert_eq!(env.rpc_url(), Some("http://localhost:5050/")); assert_eq!( env.account_address(), - Some("0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0") + Some("0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973") ); assert_eq!( env.private_key(), - Some("0x0300001800000000300000180000000000030000000000003006001800006600") + Some("0x1800000000300000180000000000030000000000003006001800006600") ); assert_eq!(env.keystore_path(), Some("test/")); assert_eq!(env.keystore_password(), Some("dojo")); diff --git a/crates/dojo-world/src/migration/mod.rs b/crates/dojo-world/src/migration/mod.rs index 1f31fd45bf..f6268548c2 100644 --- a/crates/dojo-world/src/migration/mod.rs +++ b/crates/dojo-world/src/migration/mod.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::path::PathBuf; use std::sync::Arc; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use async_trait::async_trait; use cairo_lang_starknet::casm_contract_class::CasmContractClass; use cairo_lang_starknet::contract_class::ContractClass; @@ -12,16 +12,15 @@ use starknet::core::types::{ BlockId, BlockTag, DeclareTransactionResult, FieldElement, FlattenedSierraClass, InvokeTransactionResult, StarknetError, }; -use starknet::core::utils::{ - get_contract_address, get_selector_from_name, CairoShortStringToFeltError, -}; +use starknet::core::utils::{get_contract_address, CairoShortStringToFeltError}; +use starknet::macros::{felt, selector}; use starknet::providers::{ MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, }; use starknet::signers::Signer; use thiserror::Error; -use crate::utils::{block_number_from_receipt, TransactionWaiter, TransactionWaitingError}; +use crate::utils::{TransactionWaiter, TransactionWaitingError}; pub mod class; pub mod contract; @@ -35,7 +34,6 @@ pub struct DeployOutput { pub transaction_hash: FieldElement, pub contract_address: FieldElement, pub declare: Option, - pub block_number: u64, } #[derive(Debug)] @@ -46,10 +44,12 @@ pub struct RegisterOutput { #[derive(Debug, Error)] pub enum MigrationError { + #[error("Compiling contract.")] + CompilingContract, #[error("Class already declared.")] ClassAlreadyDeclared, #[error("Contract already deployed.")] - ContractAlreadyDeployed, + ContractAlreadyDeployed(FieldElement), #[error(transparent)] Migrator(#[from] AccountError), #[error(transparent)] @@ -76,12 +76,21 @@ pub trait StateDiff { fn is_same(&self) -> bool; } +/// The transaction configuration to use when sending a transaction. +#[derive(Debug, Copy, Clone, Default)] +pub struct TxConfig { + /// The multiplier for how much the actual transaction max fee should be relative to the + /// estimated fee. If `None` is provided, the multiplier is set to `1.1`. + pub fee_estimate_multiplier: Option, +} + #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] pub trait Declarable { async fn declare( &self, account: &SingleOwnerAccount, + txn_config: TxConfig, ) -> Result< DeclareOutput, MigrationError< as Account>::SignError,

::Error>, @@ -107,13 +116,18 @@ pub trait Declarable { Err(e) => return Err(MigrationError::Provider(e)), } - let DeclareTransactionResult { transaction_hash, class_hash } = account - .declare(Arc::new(flattened_class), casm_class_hash) - .send() - .await - .map_err(MigrationError::Migrator)?; + let mut txn = account.declare(Arc::new(flattened_class), casm_class_hash); - let _ = TransactionWaiter::new(transaction_hash, account.provider()).await.unwrap(); + if let TxConfig { fee_estimate_multiplier: Some(multiplier) } = txn_config { + txn = txn.fee_estimate_multiplier(multiplier); + } + + let DeclareTransactionResult { transaction_hash, class_hash } = + txn.send().await.map_err(MigrationError::Migrator)?; + + TransactionWaiter::new(transaction_hash, account.provider()) + .await + .map_err(MigrationError::WaitingError)?; return Ok(DeclareOutput { transaction_hash, class_hash }); } @@ -129,6 +143,7 @@ pub trait Deployable: Declarable + Sync { class_hash: FieldElement, constructor_calldata: Vec, account: &SingleOwnerAccount, + txn_config: TxConfig, ) -> Result< DeployOutput, MigrationError< as Account>::SignError,

::Error>, @@ -137,7 +152,7 @@ pub trait Deployable: Declarable + Sync { P: Provider + Sync + Send, S: Signer + Sync + Send, { - let declare = match self.declare(account).await { + let declare = match self.declare(account, txn_config).await { Ok(res) => Some(res), Err(MigrationError::ClassAlreadyDeclared) => None, @@ -172,34 +187,27 @@ pub trait Deployable: Declarable + Sync { .. })) => {} - Ok(_) => return Err(MigrationError::ContractAlreadyDeployed), + Ok(_) => return Err(MigrationError::ContractAlreadyDeployed(contract_address)), Err(e) => return Err(MigrationError::Provider(e)), } - let InvokeTransactionResult { transaction_hash } = account - .execute(vec![Call { - calldata, - // devnet UDC address - to: FieldElement::from_hex_be( - "0x41a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf", - ) - .unwrap(), - selector: get_selector_from_name("deployContract").unwrap(), - }]) - .send() - .await - .map_err(MigrationError::Migrator)?; + let mut txn = account.execute(vec![Call { + calldata, + // devnet UDC address + selector: selector!("deployContract"), + to: felt!("0x41a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf"), + }]); - let txn = TransactionWaiter::new(transaction_hash, account.provider()) - .await - .map_err(MigrationError::WaitingError)?; + if let TxConfig { fee_estimate_multiplier: Some(multiplier) } = txn_config { + txn = txn.fee_estimate_multiplier(multiplier); + } + + let InvokeTransactionResult { transaction_hash } = + txn.send().await.map_err(MigrationError::Migrator)?; + + TransactionWaiter::new(transaction_hash, account.provider()).await?; - Ok(DeployOutput { - transaction_hash, - contract_address, - declare, - block_number: block_number_from_receipt(&txn), - }) + Ok(DeployOutput { transaction_hash, contract_address, declare }) } fn salt(&self) -> FieldElement; @@ -210,8 +218,9 @@ fn prepare_contract_declaration_params( ) -> Result<(FlattenedSierraClass, FieldElement)> { let flattened_class = get_flattened_class(artifact_path) .map_err(|e| anyhow!("error flattening the contract class: {e}"))?; - let compiled_class_hash = get_compiled_class_hash(artifact_path) - .map_err(|e| anyhow!("error computing compiled class hash: {e}"))?; + let compiled_class_hash = get_compiled_class_hash(artifact_path).map_err(|e| { + anyhow!("error computing compiled class hash: {} {e}", artifact_path.to_str().unwrap()) + })?; Ok((flattened_class, compiled_class_hash)) } @@ -224,8 +233,7 @@ fn get_flattened_class(artifact_path: &PathBuf) -> Result fn get_compiled_class_hash(artifact_path: &PathBuf) -> Result { let file = File::open(artifact_path)?; let casm_contract_class: ContractClass = serde_json::from_reader(file)?; - let casm_contract = CasmContractClass::from_contract_class(casm_contract_class, true) - .with_context(|| "Compilation failed.")?; + let casm_contract = CasmContractClass::from_contract_class(casm_contract_class, true)?; let res = serde_json::to_string_pretty(&casm_contract)?; let compiled_class: CompiledClass = serde_json::from_str(&res)?; Ok(compiled_class.class_hash()?) diff --git a/crates/dojo-world/src/migration/strategy.rs b/crates/dojo-world/src/migration/strategy.rs index eb68067589..eea4d9271a 100644 --- a/crates/dojo-world/src/migration/strategy.rs +++ b/crates/dojo-world/src/migration/strategy.rs @@ -17,8 +17,8 @@ use super::{DeployOutput, MigrationType, RegisterOutput}; pub struct MigrationOutput { pub world: Option, pub executor: Option, - pub systems: Option, - pub components: Option, + pub contracts: Vec, + pub models: Option, } #[derive(Debug)] @@ -26,8 +26,8 @@ pub struct MigrationStrategy { pub world_address: Option, pub world: Option, pub executor: Option, - pub systems: Vec, - pub components: Vec, + pub contracts: Vec, + pub models: Vec, } #[derive(Debug)] @@ -62,12 +62,12 @@ impl MigrationStrategy { } } - self.systems.iter().for_each(|item| match item.migration_type() { + self.contracts.iter().for_each(|item| match item.migration_type() { MigrationType::New => new += 1, MigrationType::Update => update += 1, }); - self.components.iter().for_each(|item| match item.migration_type() { + self.models.iter().for_each(|item| match item.migration_type() { MigrationType::New => new += 1, MigrationType::Update => update += 1, }); @@ -111,9 +111,9 @@ where let mut world = evaluate_contract_to_migrate(&diff.world, &artifact_paths, false)?; let mut executor = evaluate_contract_to_migrate(&diff.executor, &artifact_paths, world.is_some())?; - let components = - evaluate_components_to_migrate(&diff.components, &artifact_paths, world.is_some())?; - let systems = evaluate_systems_to_migrate(&diff.systems, &artifact_paths, world.is_some())?; + let contracts = + evaluate_contracts_to_migrate(&diff.contracts, &artifact_paths, world.is_some())?; + let models = evaluate_models_to_migrate(&diff.models, &artifact_paths, world.is_some())?; if let Some(executor) = &mut executor { executor.contract_address = @@ -134,45 +134,49 @@ where ); } - Ok(MigrationStrategy { world_address, world, executor, systems, components }) + Ok(MigrationStrategy { world_address, world, executor, contracts, models }) } -fn evaluate_systems_to_migrate( - systems: &[ClassDiff], +fn evaluate_models_to_migrate( + models: &[ClassDiff], artifact_paths: &HashMap, world_contract_will_migrate: bool, ) -> Result> { - let mut syst_to_migrate = vec![]; + let mut comps_to_migrate = vec![]; - for s in systems { - match s.remote { - Some(remote) if remote == s.local && !world_contract_will_migrate => continue, + for c in models { + match c.remote { + Some(remote) if remote == c.local && !world_contract_will_migrate => continue, _ => { - let path = find_artifact_path(&s.name, artifact_paths)?; - syst_to_migrate - .push(ClassMigration { diff: s.clone(), artifact_path: path.clone() }); + let path = + find_artifact_path(c.name.to_case(Case::Snake).as_str(), artifact_paths)?; + comps_to_migrate + .push(ClassMigration { diff: c.clone(), artifact_path: path.clone() }); } } } - Ok(syst_to_migrate) + Ok(comps_to_migrate) } -fn evaluate_components_to_migrate( - components: &[ClassDiff], +fn evaluate_contracts_to_migrate( + contracts: &[ContractDiff], artifact_paths: &HashMap, world_contract_will_migrate: bool, -) -> Result> { +) -> Result> { let mut comps_to_migrate = vec![]; - for c in components { + for c in contracts { match c.remote { Some(remote) if remote == c.local && !world_contract_will_migrate => continue, _ => { let path = find_artifact_path(c.name.to_case(Case::Snake).as_str(), artifact_paths)?; - comps_to_migrate - .push(ClassMigration { diff: c.clone(), artifact_path: path.clone() }); + comps_to_migrate.push(ContractMigration { + diff: c.clone(), + artifact_path: path.clone(), + ..Default::default() + }); } } } diff --git a/crates/dojo-world/src/migration/world.rs b/crates/dojo-world/src/migration/world.rs index 4ee8e55d62..52fd359b5d 100644 --- a/crates/dojo-world/src/migration/world.rs +++ b/crates/dojo-world/src/migration/world.rs @@ -14,8 +14,8 @@ mod tests; pub struct WorldDiff { pub world: ContractDiff, pub executor: ContractDiff, - pub contracts: Vec, - pub components: Vec, + pub contracts: Vec, + pub models: Vec, pub systems: Vec, } @@ -37,14 +37,14 @@ impl WorldDiff { }) .collect::>(); - let components = local - .components + let models = local + .models .iter() - .map(|component| ClassDiff { - name: component.name.to_string(), - local: component.class_hash, + .map(|model| ClassDiff { + name: model.name.to_string(), + local: model.class_hash, remote: remote.as_ref().and_then(|m| { - m.components.iter().find(|e| e.name == component.name).map(|s| s.class_hash) + m.models.iter().find(|e| e.name == model.name).map(|s| s.class_hash) }), }) .collect::>(); @@ -52,7 +52,7 @@ impl WorldDiff { let contracts = local .contracts .iter() - .map(|contract| ClassDiff { + .map(|contract| ContractDiff { name: contract.name.to_string(), local: contract.class_hash, remote: None, @@ -71,7 +71,7 @@ impl WorldDiff { remote: remote.map(|m| m.world.class_hash), }; - WorldDiff { world, executor, systems, contracts, components } + WorldDiff { world, executor, systems, contracts, models } } pub fn count_diffs(&self) -> usize { @@ -86,7 +86,7 @@ impl WorldDiff { } count += self.systems.iter().filter(|s| !s.is_same()).count(); - count += self.components.iter().filter(|s| !s.is_same()).count(); + count += self.models.iter().filter(|s| !s.is_same()).count(); count += self.contracts.iter().filter(|s| !s.is_same()).count(); count } @@ -97,8 +97,8 @@ impl Display for WorldDiff { writeln!(f, "{}", self.world)?; writeln!(f, "{}", self.executor)?; - for component in &self.components { - writeln!(f, "{component}")?; + for model in &self.models { + writeln!(f, "{model}")?; } for system in &self.systems { diff --git a/crates/dojo-world/src/migration/world_test.rs b/crates/dojo-world/src/migration/world_test.rs index cdd9f898c8..24118681dc 100644 --- a/crates/dojo-world/src/migration/world_test.rs +++ b/crates/dojo-world/src/migration/world_test.rs @@ -1,5 +1,5 @@ use super::*; -use crate::manifest::{Component, Contract, Manifest, System}; +use crate::manifest::{Contract, Manifest, Model, System}; #[test] fn no_diff_when_local_and_remote_are_equal() { @@ -17,9 +17,9 @@ fn no_diff_when_local_and_remote_are_equal() { ..Default::default() }; - let components = vec![Component { + let models = vec![Model { members: vec![], - name: "Component".into(), + name: "Model".into(), class_hash: 11_u32.into(), ..Default::default() }]; @@ -28,7 +28,7 @@ fn no_diff_when_local_and_remote_are_equal() { vec![System { name: "System".into(), class_hash: 22_u32.into(), ..Default::default() }]; let local = Manifest { - components, + models, world: world_contract, executor: executor_contract, systems, @@ -55,9 +55,9 @@ fn diff_when_local_and_remote_are_different() { ..Default::default() }; - let components = vec![Component { + let models = vec![Model { members: vec![], - name: "Component".into(), + name: "Model".into(), class_hash: 11_u32.into(), ..Default::default() }]; @@ -66,7 +66,7 @@ fn diff_when_local_and_remote_are_different() { vec![System { name: "System".into(), class_hash: 22_u32.into(), ..Default::default() }]; let local = Manifest { - components, + models, world: world_contract, executor: executor_contract, systems, @@ -76,7 +76,7 @@ fn diff_when_local_and_remote_are_different() { let mut remote = local.clone(); remote.world.class_hash = 44_u32.into(); remote.executor.class_hash = 55_u32.into(); - remote.components[0].class_hash = 33_u32.into(); + remote.models[0].class_hash = 33_u32.into(); let diff = WorldDiff::compute(local, Some(remote)); diff --git a/crates/dojo-world/src/utils.rs b/crates/dojo-world/src/utils.rs index babfaf7143..790ad93bab 100644 --- a/crates/dojo-world/src/utils.rs +++ b/crates/dojo-world/src/utils.rs @@ -5,8 +5,8 @@ use std::time::Duration; use futures::FutureExt; use starknet::core::types::{ - FieldElement, MaybePendingTransactionReceipt, StarknetError, TransactionReceipt, - TransactionStatus, + ExecutionResult, FieldElement, MaybePendingTransactionReceipt, PendingTransactionReceipt, + StarknetError, TransactionFinalityStatus, TransactionReceipt, }; use starknet::providers::{ MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, @@ -20,35 +20,62 @@ type GetReceiptFuture<'a, E> = Pin> pub enum TransactionWaitingError { #[error("request timed out")] Timeout, - #[error("transaction was rejected")] - TransactionRejected, + #[error("transaction reverted due to failed execution: {0}")] + TransactionReverted(String), #[error(transparent)] Provider(ProviderError), } -/// A type that waits for a transaction to achieve `status` status. The transaction will be polled -/// for every `interval` miliseconds. If the transaction does not achieved `status` status within -/// `timeout` miliseconds, an error will be returned. An error is also returned if the transaction -/// is rejected ( i.e., the transaction returns a `REJECTED` status ). -pub struct TransactionWaiter<'a, P> -where - P: Provider, -{ +/// A type that waits for a transaction to achieve the desired status. The waiter will poll for the +/// transaction receipt every `interval` miliseconds until it achieves the desired status or until +/// `timeout` is reached. +/// +/// The waiter can be configured to wait for a specific finality status (e.g, `ACCEPTED_ON_L2`), by +/// default, it only waits until the transaction is included in the _pending_ block. It can also be +/// set to check if the transaction is executed successfully or not (reverted). +/// +/// # Examples +/// +/// ```ignore +/// ues url::Url; +/// use starknet::providers::jsonrpc::HttpTransport; +/// use starknet::providers::JsonRpcClient; +/// use starknet::core::types::TransactionFinalityStatus; +/// +/// let provider = JsonRpcClient::new(HttpTransport::new(Url::parse("http://localhost:5000").unwrap())); +/// +/// let tx_hash = FieldElement::from(0xbadbeefu64); +/// let receipt = TransactionWaiter::new(tx_hash, &provider).with_finality(TransactionFinalityStatus::ACCEPTED_ON_L2).await.unwrap(); +/// ``` +#[must_use = "TransactionWaiter does nothing unless polled"] +pub struct TransactionWaiter<'a, P: Provider> { /// The hash of the transaction to wait for. tx_hash: FieldElement, - /// The status to wait for. Defaults to `TransactionStatus::AcceptedOnL2`. - status: TransactionStatus, + /// The finality status to wait for. + /// + /// If set, the waiter will wait for the transaction to achieve this finality status. + /// Otherwise, the waiter will only wait for the transaction until it is included in the + /// _pending_ block. + finality_status: Option, + /// A flag to indicate that the waited transaction must either be successfully executed or not. + /// + /// If it's set to `true`, then the transaction execution status must be `SUCCEEDED` otherwise + /// an error will be returned. However, if set to `false`, then the execution status will not + /// be considered when waiting for the transaction, meaning `REVERTED` transaction will not + /// return an error. + must_succeed: bool, /// Poll the transaction every `interval` miliseconds. Miliseconds are used so that /// we can be more precise with the polling interval. Defaults to 250ms. interval: Interval, - /// The maximum amount of time to wait for the transaction to achieve `status` status. - /// Defaults to 60 seconds. + /// The maximum amount of time to wait for the transaction to achieve the desired status. An + /// error will be returned if it is unable to finish within the `timeout` duration. Defaults to + /// 60 seconds. timeout: Duration, /// The provider to use for polling the transaction. provider: &'a P, - /// The future that get the transaction receipt. - future: Option::Error>>, - /// The time when the transaction waiter was polled. + /// The future that gets the transaction receipt. + receipt_request_fut: Option::Error>>, + /// The time when the transaction waiter was first polled. started_at: Option, } @@ -58,15 +85,15 @@ where { const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); const DEFAULT_INTERVAL: Duration = Duration::from_millis(250); - const DEFAULT_STATUS: TransactionStatus = TransactionStatus::AcceptedOnL2; pub fn new(tx: FieldElement, provider: &'a P) -> Self { Self { provider, tx_hash: tx, - future: None, started_at: None, - status: Self::DEFAULT_STATUS, + must_succeed: true, + finality_status: None, + receipt_request_fut: None, timeout: Self::DEFAULT_TIMEOUT, interval: tokio::time::interval_at( Instant::now() + Self::DEFAULT_INTERVAL, @@ -75,20 +102,17 @@ where } } - pub fn with_interval(mut self, milisecond: u64) -> Self { + pub fn with_interval(self, milisecond: u64) -> Self { let interval = Duration::from_millis(milisecond); - self.interval = tokio::time::interval_at(Instant::now() + interval, interval); - self + Self { interval: tokio::time::interval_at(Instant::now() + interval, interval), ..self } } - pub fn with_status(mut self, status: TransactionStatus) -> Self { - self.status = status; - self + pub fn with_finality(self, status: TransactionFinalityStatus) -> Self { + Self { finality_status: Some(status), ..self } } - pub fn with_timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self + pub fn with_timeout(self, timeout: Duration) -> Self { + Self { timeout, ..self } } } @@ -96,7 +120,7 @@ impl<'a, P> Future for TransactionWaiter<'a, P> where P: Provider + Send, { - type Output = Result>; + type Output = Result>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); @@ -112,42 +136,76 @@ where } } - if let Some(mut flush) = this.future.take() { + if let Some(mut flush) = this.receipt_request_fut.take() { match flush.poll_unpin(cx) { Poll::Ready(res) => match res { - Ok(MaybePendingTransactionReceipt::Receipt(receipt)) => { - match transaction_status_from_receipt(&receipt) { - TransactionStatus::Rejected => { - return Poll::Ready(Err( - TransactionWaitingError::TransactionRejected, - )); + Ok(receipt) => match &receipt { + MaybePendingTransactionReceipt::PendingReceipt(r) => { + if this.finality_status.is_none() { + if this.must_succeed { + let res = match execution_status_from_pending_receipt(r) { + ExecutionResult::Succeeded => Ok(receipt), + ExecutionResult::Reverted { reason } => { + Err(TransactionWaitingError::TransactionReverted( + reason.clone(), + )) + } + }; + return Poll::Ready(res); + } + + return Poll::Ready(Ok(receipt)); } + } + + MaybePendingTransactionReceipt::Receipt(r) => { + if let Some(finality_status) = this.finality_status { + match finality_status_from_receipt(r) { + status if status == finality_status => { + if this.must_succeed { + let res = match execution_status_from_receipt(r) { + ExecutionResult::Succeeded => Ok(receipt), + ExecutionResult::Reverted { reason } => { + Err(TransactionWaitingError::TransactionReverted( + reason.clone(), + )) + } + }; + return Poll::Ready(res); + } - status if status == this.status => return Poll::Ready(Ok(receipt)), + return Poll::Ready(Ok(receipt)); + } - _ => {} + _ => {} + } + } else { + return Poll::Ready(Ok(receipt)); + } } - } + }, - Ok(MaybePendingTransactionReceipt::PendingReceipt(_)) - | Err(ProviderError::StarknetError(StarknetErrorWithMessage { + Err(ProviderError::StarknetError(StarknetErrorWithMessage { code: MaybeUnknownErrorCode::Known(StarknetError::TransactionHashNotFound), .. })) => {} - Err(e) => return Poll::Ready(Err(TransactionWaitingError::Provider(e))), + Err(e) => { + return Poll::Ready(Err(TransactionWaitingError::Provider(e))); + } }, Poll::Pending => { - this.future = Some(flush); + this.receipt_request_fut = Some(flush); return Poll::Pending; } } } if this.interval.poll_tick(cx).is_ready() { - this.future = Some(Box::pin(this.provider.get_transaction_receipt(this.tx_hash))); + this.receipt_request_fut = + Some(Box::pin(this.provider.get_transaction_receipt(this.tx_hash))); } else { break; } @@ -157,18 +215,42 @@ where } } -pub fn transaction_status_from_receipt(receipt: &TransactionReceipt) -> TransactionStatus { +#[inline] +fn execution_status_from_receipt(receipt: &TransactionReceipt) -> &ExecutionResult { match receipt { - TransactionReceipt::Invoke(receipt) => receipt.status, - TransactionReceipt::Deploy(receipt) => receipt.status, - TransactionReceipt::Declare(receipt) => receipt.status, - TransactionReceipt::L1Handler(receipt) => receipt.status, - TransactionReceipt::DeployAccount(receipt) => receipt.status, + TransactionReceipt::Invoke(receipt) => &receipt.execution_result, + TransactionReceipt::Deploy(receipt) => &receipt.execution_result, + TransactionReceipt::Declare(receipt) => &receipt.execution_result, + TransactionReceipt::L1Handler(receipt) => &receipt.execution_result, + TransactionReceipt::DeployAccount(receipt) => &receipt.execution_result, } } -pub fn block_number_from_receipt(tx: &TransactionReceipt) -> u64 { - match tx { +#[inline] +fn execution_status_from_pending_receipt(receipt: &PendingTransactionReceipt) -> &ExecutionResult { + match receipt { + PendingTransactionReceipt::Invoke(receipt) => &receipt.execution_result, + PendingTransactionReceipt::Deploy(receipt) => &receipt.execution_result, + PendingTransactionReceipt::Declare(receipt) => &receipt.execution_result, + PendingTransactionReceipt::L1Handler(receipt) => &receipt.execution_result, + PendingTransactionReceipt::DeployAccount(receipt) => &receipt.execution_result, + } +} + +#[inline] +fn finality_status_from_receipt(receipt: &TransactionReceipt) -> TransactionFinalityStatus { + match receipt { + TransactionReceipt::Invoke(receipt) => receipt.finality_status, + TransactionReceipt::Deploy(receipt) => receipt.finality_status, + TransactionReceipt::Declare(receipt) => receipt.finality_status, + TransactionReceipt::L1Handler(receipt) => receipt.finality_status, + TransactionReceipt::DeployAccount(receipt) => receipt.finality_status, + } +} + +#[inline] +pub fn block_number_from_receipt(receipt: &TransactionReceipt) -> u64 { + match receipt { TransactionReceipt::Invoke(tx) => tx.block_number, TransactionReceipt::L1Handler(tx) => tx.block_number, TransactionReceipt::Declare(tx) => tx.block_number, diff --git a/crates/katana/Cargo.toml b/crates/katana/Cargo.toml index 1c42e492f4..a54c1f8672 100644 --- a/crates/katana/Cargo.toml +++ b/crates/katana/Cargo.toml @@ -10,16 +10,13 @@ version.workspace = true clap.workspace = true clap_complete.workspace = true console.workspace = true -env_logger.workspace = true katana-core = { path = "core" } katana-rpc = { path = "rpc" } -log.workspace = true starknet_api.workspace = true tokio.workspace = true +tracing-subscriber.workspace = true +tracing.workspace = true +url.workspace = true [dev-dependencies] assert_matches = "1.5.0" - -[[bin]] -name = "katana" -path = "src/main.rs" diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index 95b7189dd8..9a298c4664 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -8,26 +8,25 @@ version.workspace = true [dependencies] anyhow.workspace = true -async-trait.workspace = true -auto_impl = "1.1.0" blockifier.workspace = true -cairo-lang-casm = "2.1.1" -cairo-lang-starknet = "2.1.1" +cairo-lang-casm = "2.2.0" +cairo-lang-starknet = "2.2.0" cairo-vm.workspace = true convert_case.workspace = true flate2.workspace = true -futures = "0.3" +futures.workspace = true lazy_static = "1.4.0" -parking_lot = "0.12.1" +parking_lot.workspace = true rand = { version = "0.8.5", features = [ "small_rng" ] } serde.workspace = true -serde_json = "1.0.70" +serde_json.workspace = true serde_with.workspace = true starknet.workspace = true starknet_api.workspace = true thiserror.workspace = true tokio.workspace = true -tracing = "0.1.34" +tracing.workspace = true +url.workspace = true [dev-dependencies] assert_matches = "1.5.0" diff --git a/crates/katana/core/src/accounts.rs b/crates/katana/core/src/accounts.rs index 2278d90ce7..88a1df43dd 100644 --- a/crates/katana/core/src/accounts.rs +++ b/crates/katana/core/src/accounts.rs @@ -19,7 +19,7 @@ use starknet_api::patricia_key; use crate::constants::{ DEFAULT_ACCOUNT_CONTRACT, DEFAULT_ACCOUNT_CONTRACT_CLASS_HASH, FEE_TOKEN_ADDRESS, }; -use crate::db::Db; +use crate::db::Database; #[serde_as] #[derive(Debug, Clone, Serialize)] @@ -58,14 +58,14 @@ impl Account { } // TODO: separate fund logic from this struct - implement FeeToken type - pub fn deploy_and_fund(&self, state: &mut S) -> StateResult<()> { + pub fn deploy_and_fund(&self, state: &mut dyn Database) -> StateResult<()> { self.declare(state)?; self.deploy(state)?; self.fund(state); Ok(()) } - fn deploy(&self, state: &mut S) -> StateResult<()> { + fn deploy(&self, state: &mut dyn Database) -> StateResult<()> { let address = ContractAddress(patricia_key!(self.address)); // set the class hash at the account address state.set_class_hash_at(address, ClassHash(self.class_hash.into()))?; @@ -80,7 +80,7 @@ impl Account { Ok(()) } - fn fund(&self, state: &mut S) { + fn fund(&self, state: &mut dyn Database) { state.set_storage_at( ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)), get_storage_var_address("ERC20_balances", &[self.address.into()]).unwrap(), @@ -88,7 +88,7 @@ impl Account { ); } - fn declare(&self, state: &mut S) -> StateResult<()> { + fn declare(&self, state: &mut dyn Database) -> StateResult<()> { let class_hash = ClassHash(self.class_hash.into()); if state.get_compiled_contract_class(&class_hash).is_ok() { @@ -155,7 +155,7 @@ impl DevAccountGenerator { let mut private_key_bytes = [0u8; 32]; rng.fill_bytes(&mut private_key_bytes); - private_key_bytes[0] %= 0x9; + private_key_bytes[0] %= 0x8; seed = private_key_bytes; let private_key = FieldElement::from_bytes_be(&private_key_bytes) diff --git a/crates/katana/core/src/backend/config.rs b/crates/katana/core/src/backend/config.rs index 3267b8ffe0..c6a7131fe0 100644 --- a/crates/katana/core/src/backend/config.rs +++ b/crates/katana/core/src/backend/config.rs @@ -3,6 +3,7 @@ use starknet_api::block::{BlockNumber, BlockTimestamp}; use starknet_api::core::{ChainId, ContractAddress, PatriciaKey}; use starknet_api::hash::StarkHash; use starknet_api::patricia_key; +use url::Url; use crate::constants::{ DEFAULT_GAS_PRICE, DEFAULT_INVOKE_MAX_STEPS, DEFAULT_VALIDATE_MAX_STEPS, FEE_TOKEN_ADDRESS, @@ -14,10 +15,11 @@ use crate::env::{get_default_vm_resource_fee_cost, BlockContextGenerator}; #[derive(Debug)] pub struct StarknetConfig { pub seed: [u8; 32], - pub auto_mine: bool, pub total_accounts: u8, pub disable_fee: bool, pub env: Environment, + pub fork_rpc_url: Option, + pub fork_block_number: Option, pub init_state: Option, } @@ -47,9 +49,10 @@ impl Default for StarknetConfig { Self { init_state: None, seed: [0; 32], - auto_mine: true, total_accounts: 10, disable_fee: false, + fork_rpc_url: None, + fork_block_number: None, env: Environment::default(), } } diff --git a/crates/katana/core/src/backend/executor.rs b/crates/katana/core/src/backend/executor.rs deleted file mode 100644 index 61fcbde83a..0000000000 --- a/crates/katana/core/src/backend/executor.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::sync::Arc; - -use blockifier::block_context::BlockContext; -use blockifier::execution::entry_point::CallInfo; -use blockifier::state::cached_state::CachedState; -use blockifier::state::state_api::StateReader; -use blockifier::transaction::errors::TransactionExecutionError; -use blockifier::transaction::objects::{ResourcesMapping, TransactionExecutionInfo}; -use blockifier::transaction::transaction_execution::Transaction as ExecutionTransaction; -use blockifier::transaction::transactions::ExecutableTransaction; -use convert_case::{Case, Casing}; -use parking_lot::RwLock; -use starknet::core::types::{Event, FieldElement, MsgToL1}; -use tokio::sync::RwLock as AsyncRwLock; -use tracing::{info, trace, warn}; - -use super::state::MemDb; -use super::storage::block::{PartialBlock, PartialHeader}; -use super::storage::transaction::{RejectedTransaction, Transaction, TransactionOutput}; -use super::storage::BlockchainStorage; -use crate::backend::storage::transaction::{DeclareTransaction, KnownTransaction}; -use crate::env::Env; - -#[derive(Debug)] -pub struct PendingBlockExecutor { - pub parent_hash: FieldElement, - /// The state of the pending block. It is the state that the - /// transaction included in the pending block will be executed on. - /// The changes made after the execution of a transaction will be - /// persisted for the next included transaction. - pub state: CachedState, - pub storage: Arc>, - pub env: Arc>, - pub transactions: Vec>, - pub outputs: Vec, -} - -impl PendingBlockExecutor { - pub fn new( - parent_hash: FieldElement, - state: MemDb, - env: Arc>, - storage: Arc>, - ) -> Self { - Self { - env, - storage, - parent_hash, - outputs: Vec::new(), - transactions: Vec::new(), - state: CachedState::new(state), - } - } - - pub fn as_block(&self) -> PartialBlock { - let block_context = &self.env.read().block; - - let header = PartialHeader { - parent_hash: self.parent_hash, - gas_price: block_context.gas_price, - number: block_context.block_number.0, - timestamp: block_context.block_timestamp.0, - sequencer_address: (*block_context.sequencer_address.0.key()).into(), - }; - - PartialBlock { - header, - outputs: self.outputs.clone(), - transactions: self.transactions.clone(), - } - } - - // Add a transaction to the executor. The transaction will be executed - // on the pending state. The transaction will be added to the pending block - // if it passes the validation logic. Otherwise, the transaction will be - // rejected. On both cases, the transaction will still be stored in the - // storage. - pub async fn add_transaction(&mut self, transaction: Transaction, charge_fee: bool) -> bool { - let transaction_hash = transaction.hash(); - - info!("Transaction received | Hash: {transaction_hash:#x}"); - - let res = execute_transaction( - ExecutionTransaction::AccountTransaction(transaction.clone().into()), - &mut self.state, - &self.env.read().block, - charge_fee, - ); - - match res { - Ok(execution_info) => { - trace!( - "Transaction resource usage: {}", - pretty_print_resources(&execution_info.actual_resources) - ); - - // Because `State` trait from `blockifier` doesn't have a method to set the - // `sierra_class` of a contract, we need to do it manually. - if let Transaction::Declare(DeclareTransaction { - inner, - sierra_class: Some(sierra_class), - .. - }) = &transaction - { - let class_hash = inner.class_hash(); - self.state.state.sierra_classes.insert(class_hash, sierra_class.clone()); - } - - let executed_tx = Arc::new(ExecutedTransaction::new(transaction, execution_info)); - - trace_events(&executed_tx.output.events); - - self.outputs.push(executed_tx.output.clone()); - self.transactions.push(executed_tx); - - true - } - - Err(err) => { - self.storage.write().await.transactions.insert( - transaction_hash, - KnownTransaction::Rejected(Box::new(RejectedTransaction { - transaction: transaction.into(), - execution_error: err.to_string(), - })), - ); - - false - } - } - } -} - -#[derive(Debug)] -pub struct ExecutedTransaction { - pub inner: Transaction, - pub output: TransactionOutput, - pub execution_info: TransactionExecutionInfo, -} - -impl ExecutedTransaction { - pub fn new(transaction: Transaction, execution_info: TransactionExecutionInfo) -> Self { - let actual_fee = execution_info.actual_fee.0; - let events = Self::events(&execution_info); - let messages_sent = Self::l2_to_l1_messages(&execution_info); - - Self { - inner: transaction, - execution_info, - output: TransactionOutput { actual_fee, events, messages_sent }, - } - } - - fn events(execution_info: &TransactionExecutionInfo) -> Vec { - let mut events: Vec = vec![]; - - fn get_events_recursively(call_info: &CallInfo) -> Vec { - let mut events: Vec = vec![]; - - events.extend(call_info.execution.events.iter().map(|e| Event { - from_address: (*call_info.call.storage_address.0.key()).into(), - data: e.event.data.0.iter().map(|d| (*d).into()).collect(), - keys: e.event.keys.iter().map(|k| k.0.into()).collect(), - })); - - call_info.inner_calls.iter().for_each(|call| { - events.extend(get_events_recursively(call)); - }); - - events - } - - if let Some(ref call) = execution_info.validate_call_info { - events.extend(get_events_recursively(call)); - } - - if let Some(ref call) = execution_info.execute_call_info { - events.extend(get_events_recursively(call)); - } - - if let Some(ref call) = execution_info.fee_transfer_call_info { - events.extend(get_events_recursively(call)); - } - - events - } - - fn l2_to_l1_messages(execution_info: &TransactionExecutionInfo) -> Vec { - let mut messages = vec![]; - - fn get_messages_recursively(info: &CallInfo) -> Vec { - let mut messages = vec![]; - - messages.extend(info.execution.l2_to_l1_messages.iter().map(|m| MsgToL1 { - to_address: - FieldElement::from_byte_slice_be(m.message.to_address.0.as_bytes()).unwrap(), - from_address: (*info.call.caller_address.0.key()).into(), - payload: m.message.payload.0.iter().map(|p| (*p).into()).collect(), - })); - - info.inner_calls.iter().for_each(|call| { - messages.extend(get_messages_recursively(call)); - }); - - messages - } - - if let Some(ref info) = execution_info.validate_call_info { - messages.extend(get_messages_recursively(info)); - } - - if let Some(ref info) = execution_info.execute_call_info { - messages.extend(get_messages_recursively(info)); - } - - if let Some(ref info) = execution_info.fee_transfer_call_info { - messages.extend(get_messages_recursively(info)); - } - - messages - } -} - -pub fn execute_transaction( - transaction: ExecutionTransaction, - pending_state: &mut CachedState, - block_context: &BlockContext, - charge_fee: bool, -) -> Result { - let res = match transaction { - ExecutionTransaction::AccountTransaction(tx) => { - tx.execute(pending_state, block_context, charge_fee) - } - ExecutionTransaction::L1HandlerTransaction(tx) => { - tx.execute(pending_state, block_context, charge_fee) - } - }; - - match res { - Ok(exec_info) => { - if let Some(err) = &exec_info.revert_error { - let formatted_err = format!("{:?}", err).replace("\\n", "\n"); - warn!("Transaction execution error: {formatted_err}"); - } - Ok(exec_info) - } - Err(err) => { - warn!("Transaction validation error: {err:?}"); - Err(err) - } - } -} - -pub fn pretty_print_resources(resources: &ResourcesMapping) -> String { - let mut mapped_strings: Vec<_> = resources - .0 - .iter() - .filter_map(|(k, v)| match k.as_str() { - "l1_gas_usage" => Some(format!("L1 Gas: {}", v)), - "range_check_builtin" => Some(format!("Range Checks: {}", v)), - "ecdsa_builtin" => Some(format!("ECDSA: {}", v)), - "n_steps" => None, - "pedersen_builtin" => Some(format!("Pedersen: {}", v)), - "bitwise_builtin" => Some(format!("Bitwise: {}", v)), - "keccak_builtin" => Some(format!("Keccak: {}", v)), - _ => Some(format!("{}: {}", k.to_case(Case::Title), v)), - }) - .collect::>(); - - // Sort the strings alphabetically - mapped_strings.sort(); - - // Prepend "Steps" if it exists, so it is always first - if let Some(steps) = resources.0.get("n_steps") { - mapped_strings.insert(0, format!("Steps: {}", steps)); - } - - mapped_strings.join(" | ") -} - -pub fn trace_events(events: &[Event]) { - for e in events { - let formatted_keys = - e.keys.iter().map(|k| format!("{k:#x}")).collect::>().join(", "); - - trace!("Event emitted keys=[{}]", formatted_keys); - } -} diff --git a/crates/katana/core/src/backend/in_memory_db.rs b/crates/katana/core/src/backend/in_memory_db.rs new file mode 100644 index 0000000000..7f1654698d --- /dev/null +++ b/crates/katana/core/src/backend/in_memory_db.rs @@ -0,0 +1,348 @@ +use std::collections::{BTreeMap, HashMap}; + +use anyhow::Result; +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::cached_state::CommitmentStateDiff; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::{State, StateReader, StateResult}; +use starknet::core::types::FlattenedSierraClass; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::patricia_key; +use starknet_api::state::StorageKey; + +use crate::constants::{ + ERC20_CONTRACT, ERC20_CONTRACT_CLASS_HASH, FEE_TOKEN_ADDRESS, UDC_ADDRESS, UDC_CLASS_HASH, + UDC_CONTRACT, +}; +use crate::db::cached::{AsCachedDb, CachedDb, ClassRecord, MaybeAsCachedDb, StorageRecord}; +use crate::db::serde::state::{ + SerializableClassRecord, SerializableState, SerializableStorageRecord, +}; +use crate::db::{AsStateRefDb, Database, StateExt, StateExtRef, StateRefDb}; + +/// A in memory state database implementation with empty cache db. +#[derive(Clone, Debug)] +pub struct MemDb { + pub db: CachedDb<()>, +} + +impl MemDb { + pub fn new() -> Self { + Self { db: CachedDb::new(()) } + } +} + +impl Default for MemDb { + fn default() -> Self { + let mut state = Self::new(); + deploy_fee_contract(&mut state); + deploy_universal_deployer_contract(&mut state); + state + } +} + +impl State for MemDb { + fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { + let current_nonce = self.get_nonce_at(contract_address)?; + let current_nonce_as_u64 = usize::try_from(current_nonce.0)? as u64; + let next_nonce_val = 1_u64 + current_nonce_as_u64; + let next_nonce = Nonce(StarkFelt::from(next_nonce_val)); + self.db.storage.entry(contract_address).or_default().nonce = next_nonce; + Ok(()) + } + + fn set_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + value: StarkFelt, + ) { + self.db.storage.entry(contract_address).or_default().storage.insert(key, value); + } + + fn set_class_hash_at( + &mut self, + contract_address: ContractAddress, + class_hash: ClassHash, + ) -> StateResult<()> { + if contract_address == ContractAddress::default() { + return Err(StateError::OutOfRangeContractAddress); + } + self.db.contracts.insert(contract_address, class_hash); + Ok(()) + } + + fn set_compiled_class_hash( + &mut self, + class_hash: ClassHash, + compiled_class_hash: CompiledClassHash, + ) -> StateResult<()> { + if !self.db.classes.contains_key(&class_hash) { + return Err(StateError::UndeclaredClassHash(class_hash)); + } + self.db.classes.entry(class_hash).and_modify(|r| r.compiled_hash = compiled_class_hash); + Ok(()) + } + + fn set_contract_class( + &mut self, + class_hash: &ClassHash, + contract_class: ContractClass, + ) -> StateResult<()> { + let compiled_hash = CompiledClassHash(class_hash.0); + self.db.classes.insert(*class_hash, ClassRecord { class: contract_class, compiled_hash }); + Ok(()) + } + + fn to_state_diff(&self) -> CommitmentStateDiff { + unreachable!("to_state_diff should not be called on MemDb") + } +} + +impl StateReader for MemDb { + fn get_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + let value = self + .db + .storage + .get(&contract_address) + .and_then(|r| r.storage.get(&key)) + .copied() + .unwrap_or_default(); + Ok(value) + } + + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + let nonce = self.db.storage.get(&contract_address).map(|r| r.nonce).unwrap_or_default(); + Ok(nonce) + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + self.db + .classes + .get(class_hash) + .map(|r| r.class.clone()) + .ok_or(StateError::UndeclaredClassHash(*class_hash)) + } + + fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { + Ok(self.db.contracts.get(&contract_address).cloned().unwrap_or_default()) + } + + fn get_compiled_class_hash( + &mut self, + class_hash: ClassHash, + ) -> StateResult { + self.db + .classes + .get(&class_hash) + .map(|r| r.compiled_hash) + .ok_or(StateError::UndeclaredClassHash(class_hash)) + } +} + +impl StateExtRef for MemDb { + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { + if let ContractClass::V0(_) = self.get_compiled_contract_class(class_hash)? { + return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); + }; + + self.db + .sierra_classes + .get(class_hash) + .cloned() + .ok_or(StateError::StateReadError("Missing Sierra class".to_string())) + } +} + +impl StateExt for MemDb { + fn set_sierra_class( + &mut self, + class_hash: ClassHash, + sierra_class: FlattenedSierraClass, + ) -> StateResult<()> { + // check the class hash must not be a legacy contract + if let ContractClass::V0(_) = self.get_compiled_contract_class(&class_hash)? { + return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); + }; + self.db.sierra_classes.insert(class_hash, sierra_class); + Ok(()) + } +} + +impl AsStateRefDb for MemDb { + fn as_ref_db(&self) -> StateRefDb { + StateRefDb::new(MemDb { db: self.db.clone() }) + } +} + +impl MaybeAsCachedDb for MemDb { + fn maybe_as_cached_db(&self) -> Option { + Some(CachedDb { + db: (), + classes: self.db.classes.clone(), + storage: self.db.storage.clone(), + contracts: self.db.contracts.clone(), + sierra_classes: self.db.sierra_classes.clone(), + }) + } +} + +impl Database for MemDb { + fn dump_state(&self) -> Result { + let mut serializable = SerializableState::default(); + + self.db.storage.iter().for_each(|(addr, storage)| { + let mut record = SerializableStorageRecord { + storage: BTreeMap::new(), + nonce: storage.nonce.0.into(), + }; + + storage.storage.iter().for_each(|(key, value)| { + record.storage.insert((*key.0.key()).into(), (*value).into()); + }); + + serializable.storage.insert((*addr.0.key()).into(), record); + }); + + self.db.classes.iter().for_each(|(class_hash, class_record)| { + serializable.classes.insert( + class_hash.0.into(), + SerializableClassRecord { + class: class_record.class.clone().into(), + compiled_hash: class_record.compiled_hash.0.into(), + }, + ); + }); + + self.db.contracts.iter().for_each(|(address, class_hash)| { + serializable.contracts.insert((*address.0.key()).into(), class_hash.0.into()); + }); + + self.db.sierra_classes.iter().for_each(|(class_hash, class)| { + serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); + }); + + Ok(serializable) + } + + fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { + self.db.storage.entry(addr).or_default().nonce = nonce; + } +} + +fn deploy_fee_contract(state: &mut MemDb) { + let address = ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)); + let hash = ClassHash(*ERC20_CONTRACT_CLASS_HASH); + let compiled_hash = CompiledClassHash(*ERC20_CONTRACT_CLASS_HASH); + + state.db.classes.insert(hash, ClassRecord { class: (*ERC20_CONTRACT).clone(), compiled_hash }); + state.db.contracts.insert(address, hash); + state + .db + .storage + .insert(address, StorageRecord { nonce: Nonce(1_u128.into()), storage: HashMap::new() }); +} + +fn deploy_universal_deployer_contract(state: &mut MemDb) { + let address = ContractAddress(patricia_key!(*UDC_ADDRESS)); + let hash = ClassHash(*UDC_CLASS_HASH); + let compiled_hash = CompiledClassHash(*UDC_CLASS_HASH); + + state.db.classes.insert(hash, ClassRecord { class: (*UDC_CONTRACT).clone(), compiled_hash }); + state.db.contracts.insert(address, hash); + state + .db + .storage + .insert(address, StorageRecord { nonce: Nonce(1_u128.into()), storage: HashMap::new() }); +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use starknet_api::core::{ClassHash, PatriciaKey}; + use starknet_api::stark_felt; + + use super::*; + use crate::backend::in_memory_db::MemDb; + use crate::constants::UDC_CONTRACT; + // use crate::db::cached::CachedStateWrapper; + use crate::execution::ExecutionOutcome; + + #[test] + fn dump_and_load_state() { + let mut state = MemDb::new(); + + let class_hash = ClassHash(stark_felt!("0x1")); + let address = ContractAddress(patricia_key!("0x1")); + let storage_key = StorageKey(patricia_key!("0x77")); + let storage_val = stark_felt!("0x66"); + let contract = (*UDC_CONTRACT).clone(); + let compiled_hash = CompiledClassHash(class_hash.0); + + state.set_contract_class(&class_hash, (*UDC_CONTRACT).clone()).unwrap(); + state.set_compiled_class_hash(class_hash, CompiledClassHash(class_hash.0)).unwrap(); + state.set_class_hash_at(address, class_hash).unwrap(); + state.set_storage_at(address, storage_key, storage_val); + + let dump = state.dump_state().expect("should dump state"); + + let mut new_state = MemDb::new(); + new_state.load_state(dump).expect("should load state"); + + assert_eq!(new_state.get_compiled_contract_class(&class_hash).unwrap(), contract); + assert_eq!(new_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); + assert_eq!(new_state.get_class_hash_at(address).unwrap(), class_hash); + assert_eq!(new_state.get_storage_at(address, storage_key).unwrap(), storage_val); + } + + #[test] + fn apply_state_update() { + let mut old_state = MemDb::new(); + + let class_hash = ClassHash(stark_felt!("0x1")); + let address = ContractAddress(patricia_key!("0x1")); + let storage_key = StorageKey(patricia_key!("0x77")); + let storage_val = stark_felt!("0x66"); + let contract = (*UDC_CONTRACT).clone(); + let compiled_hash = CompiledClassHash(class_hash.0); + + let execution_outcome = ExecutionOutcome { + state_diff: CommitmentStateDiff { + address_to_class_hash: [(address, class_hash)].into(), + address_to_nonce: [].into(), + storage_updates: [(address, [(storage_key, storage_val)].into())].into(), + class_hash_to_compiled_class_hash: [(class_hash, CompiledClassHash(class_hash.0))] + .into(), + }, + transactions: vec![], + declared_classes: HashMap::from([(class_hash, (*UDC_CONTRACT).clone())]), + declared_sierra_classes: HashMap::new(), + }; + + assert_matches!( + old_state.get_compiled_contract_class(&class_hash), + Err(StateError::UndeclaredClassHash(_)) + ); + assert_matches!( + old_state.get_compiled_class_hash(class_hash), + Err(StateError::UndeclaredClassHash(_)) + ); + assert_eq!(old_state.get_class_hash_at(address).unwrap(), ClassHash::default()); + assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), StarkFelt::default()); + + execution_outcome.apply_to(&mut old_state); + + assert_eq!(old_state.get_compiled_contract_class(&class_hash).unwrap(), contract); + assert_eq!(old_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); + assert_eq!(old_state.get_class_hash_at(address).unwrap(), class_hash); + assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), storage_val); + } +} diff --git a/crates/katana/core/src/backend/mod.rs b/crates/katana/core/src/backend/mod.rs index 95fadf1772..a57690686a 100644 --- a/crates/katana/core/src/backend/mod.rs +++ b/crates/katana/core/src/backend/mod.rs @@ -8,41 +8,47 @@ use blockifier::execution::entry_point::{ use blockifier::execution::errors::EntryPointExecutionError; use blockifier::fee::fee_utils::{calculate_l1_gas_by_vm_usage, extract_l1_gas_and_vm_usage}; use blockifier::state::cached_state::{CachedState, MutRefState}; -use blockifier::state::state_api::State; -use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::state::state_api::StateReader; use blockifier::transaction::errors::TransactionExecutionError; use blockifier::transaction::objects::AccountTransactionContext; -use blockifier::transaction::transaction_execution::Transaction as ExecutionTransaction; use flate2::write::GzEncoder; use flate2::Compression; use parking_lot::RwLock; -use starknet::core::types::{BlockId, BlockTag, FeeEstimate}; -use starknet_api::block::BlockTimestamp; -use starknet_api::core::{ContractAddress, EntryPointSelector}; -use starknet_api::hash::StarkFelt; -use starknet_api::state::StorageKey; +use starknet::core::types::{ + BlockId, BlockTag, FeeEstimate, MaybePendingBlockWithTxHashes, TransactionFinalityStatus, +}; +use starknet::core::utils::parse_cairo_short_string; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider}; +use starknet_api::block::{BlockNumber, BlockTimestamp}; +use starknet_api::core::{ChainId, ContractAddress, EntryPointSelector, PatriciaKey}; +use starknet_api::hash::StarkHash; +use starknet_api::patricia_key; use starknet_api::transaction::Calldata; use tokio::sync::RwLock as AsyncRwLock; -use tracing::{error, info, warn}; +use tracing::{info, trace, warn}; pub mod config; pub mod contract; -pub mod executor; -pub mod state; +pub mod in_memory_db; pub mod storage; use self::config::StarknetConfig; -use self::executor::{execute_transaction, PendingBlockExecutor}; use self::storage::block::{Block, PartialHeader}; -use self::storage::transaction::{IncludedTransaction, Transaction, TransactionStatus}; -use self::storage::{BlockchainStorage, InMemoryBlockStates}; +use self::storage::transaction::{IncludedTransaction, Transaction}; +use self::storage::{Blockchain, InMemoryBlockStates, Storage}; use crate::accounts::{Account, DevAccountGenerator}; -use crate::backend::state::{MemDb, StateExt}; +use crate::backend::in_memory_db::MemDb; +use crate::backend::storage::transaction::KnownTransaction; use crate::constants::DEFAULT_PREFUNDED_ACCOUNT_BALANCE; +use crate::db::cached::CachedStateWrapper; use crate::db::serde::state::SerializableState; -use crate::db::Db; +use crate::db::{Database, StateRefDb}; use crate::env::{BlockContextGenerator, Env}; +use crate::execution::{ExecutionOutcome, MaybeInvalidExecutedTransaction, TransactionExecutor}; +use crate::fork::db::ForkedDb; use crate::sequencer_error::SequencerError; +use crate::service::block_producer::MinedBlockOutcome; use crate::utils::{convert_state_diff_to_rpc_state_diff, get_current_timestamp}; pub struct ExternalFunctionCall { @@ -52,59 +58,108 @@ pub struct ExternalFunctionCall { } pub struct Backend { + /// The config used to generate the backend. pub config: RwLock, /// stores all block related data in memory - pub storage: Arc>, - // TEMP: pending block for transaction execution - pub pending_block: AsyncRwLock>, + pub blockchain: Blockchain, /// Historic states of previous blocks pub states: AsyncRwLock, /// The chain environment values. pub env: Arc>, pub block_context_generator: RwLock, - pub state: AsyncRwLock, + /// The latest state. + pub state: Arc>, /// Prefunded dev accounts pub accounts: Vec, } impl Backend { - pub fn new(config: StarknetConfig) -> Self { - let block_context = config.block_context(); + pub async fn new(config: StarknetConfig) -> Self { + let mut block_context = config.block_context(); let block_context_generator = config.block_context_generator(); - let mut state = MemDb::default(); - - let storage = BlockchainStorage::new(&block_context); - let states = InMemoryBlockStates::default(); - let env = Env { block: block_context }; - - if let Some(ref init_state) = config.init_state { - state.load_state(init_state.clone()).expect("failed to load initial state"); - info!("Successfully loaded initial state"); - } - let accounts = DevAccountGenerator::new(config.total_accounts) .with_seed(config.seed) .with_balance((*DEFAULT_PREFUNDED_ACCOUNT_BALANCE).into()) .generate(); + let (state, storage): (Arc>, Arc>) = + if let Some(forked_url) = config.fork_rpc_url.clone() { + let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(forked_url.clone()))); + + let forked_chain_id = provider.chain_id().await.unwrap(); + let forked_block_id = config + .fork_block_number + .map(BlockId::Number) + .unwrap_or(BlockId::Tag(BlockTag::Latest)); + + let block = provider.get_block_with_tx_hashes(forked_block_id).await.unwrap(); + + let MaybePendingBlockWithTxHashes::Block(block) = block else { + panic!("block to be forked is a pending block") + }; + + block_context.block_number = BlockNumber(block.block_number); + block_context.block_timestamp = BlockTimestamp(block.timestamp); + block_context.sequencer_address = + ContractAddress(patricia_key!(block.sequencer_address)); + block_context.chain_id = + ChainId(parse_cairo_short_string(&forked_chain_id).unwrap()); + + let state = ForkedDb::new(Arc::clone(&provider), forked_block_id); + + trace!( + target: "backend", + "forking chain `{}` at block {} from {}", + parse_cairo_short_string(&forked_chain_id).unwrap(), + block.block_number, + forked_url + ); + + ( + Arc::new(AsyncRwLock::new(state)), + Arc::new(RwLock::new(Storage::new_forked( + block.block_number, + block.block_hash, + ))), + ) + } else { + ( + Arc::new(AsyncRwLock::new(MemDb::default())), + Arc::new(RwLock::new(Storage::new(&block_context))), + ) + }; + for acc in &accounts { - acc.deploy_and_fund(&mut state).expect("should be able to deploy and fund dev account"); + acc.deploy_and_fund(&mut *state.write().await) + .expect("should be able to deploy and fund dev account"); + } + + if let Some(ref init_state) = config.init_state { + state + .write() + .await + .load_state(init_state.clone()) + .expect("failed to load initial state"); + info!(target: "backend", "Successfully loaded initial state"); } + let blockchain = Blockchain::new(storage); + let states = InMemoryBlockStates::default(); + let env = Env { block: block_context }; + Self { + state, env: Arc::new(RwLock::new(env)), - state: AsyncRwLock::new(state), config: RwLock::new(config), states: AsyncRwLock::new(states), - storage: Arc::new(AsyncRwLock::new(storage)), + blockchain, block_context_generator: RwLock::new(block_context_generator), - pending_block: AsyncRwLock::new(None), accounts, } } - /// Get the current state. + /// Get the current state in a serializable format. pub async fn serialize_state(&self) -> Result { self.state.read().await.dump_state().map_err(|_| SequencerError::StateSerialization) } @@ -122,129 +177,127 @@ impl Backend { pub fn estimate_fee( &self, - transaction: AccountTransaction, - state: MemDb, - ) -> Result { - let mut state = CachedState::new(state); + transactions: Vec, + state: StateRefDb, + ) -> Result, TransactionExecutionError> { + let mut state = CachedStateWrapper::new(state); + let block_context = self.env.read().block.clone(); + + let mut estimations = Vec::with_capacity(transactions.len()); + + let results = TransactionExecutor::new(&mut state, &block_context, false) + .with_error_log() + .execute_many(transactions); + + for res in results { + let exec_info = res?; + + if exec_info.revert_error.is_some() { + // TEMP: change this once `Reverted` transaction error is no longer `String`. + return Err(TransactionExecutionError::ExecutionError( + EntryPointExecutionError::ExecutionFailed { error_data: vec![] }, + )); + } - let exec_info = execute_transaction( - ExecutionTransaction::AccountTransaction(transaction), - &mut state, - &self.env.read().block, - true, - )?; - - if exec_info.revert_error.is_some() { - // TEMP: change this once `Reverted` transaction error is no longer `String`. - return Err(TransactionExecutionError::ExecutionError( - EntryPointExecutionError::ExecutionFailed { error_data: vec![] }, - )); - } + let (l1_gas_usage, vm_resources) = + extract_l1_gas_and_vm_usage(&exec_info.actual_resources); + let l1_gas_by_vm_usage = calculate_l1_gas_by_vm_usage(&block_context, &vm_resources)?; + let total_l1_gas_usage = l1_gas_usage as f64 + l1_gas_by_vm_usage; - let (l1_gas_usage, vm_resources) = extract_l1_gas_and_vm_usage(&exec_info.actual_resources); - let l1_gas_by_vm_usage = - calculate_l1_gas_by_vm_usage(&self.env.read().block, &vm_resources)?; - let total_l1_gas_usage = l1_gas_usage as f64 + l1_gas_by_vm_usage; + let gas_price = block_context.gas_price as u64; - let gas_price = self.env.read().block.gas_price as u64; + estimations.push(FeeEstimate { + gas_consumed: total_l1_gas_usage.ceil() as u64, + gas_price, + overall_fee: total_l1_gas_usage.ceil() as u64 * gas_price, + }) + } - Ok(FeeEstimate { - gas_consumed: total_l1_gas_usage.ceil() as u64, - gas_price, - overall_fee: total_l1_gas_usage.ceil() as u64 * gas_price, - }) + Ok(estimations) } - // execute the tx - pub async fn handle_transaction(&self, transaction: Transaction) { - let is_valid = if let Some(pending_block) = self.pending_block.write().await.as_mut() { - let charge_fee = !self.config.read().disable_fee; - pending_block.add_transaction(transaction, charge_fee).await - } else { - return error!("Unable to process transaction: no pending block"); - }; + /// Mines a new block based on the provided execution outcome. + /// This method should only be called by the + /// [IntervalBlockProducer](crate::service::block_producer::IntervalBlockProducer) when the node + /// is running in `interval` mining mode. + pub async fn mine_pending_block( + &self, + execution_outcome: ExecutionOutcome, + ) -> (MinedBlockOutcome, StateRefDb) { + let outcome = self.do_mine_block(execution_outcome).await; + let new_state = self.state.read().await.as_ref_db(); + (outcome, new_state) + } - if is_valid && self.config.read().auto_mine { - self.mine_block().await; - self.open_pending_block().await; - } + /// Updates the block context and mines an empty block. + pub async fn mine_empty_block(&self) -> MinedBlockOutcome { + self.update_block_context(); + self.do_mine_block(ExecutionOutcome::default()).await } - // Generates a new block from the pending block and stores it in the storage. - pub async fn mine_block(&self) { - let pending = self.pending_block.write().await.take(); + pub async fn do_mine_block(&self, execution_outcome: ExecutionOutcome) -> MinedBlockOutcome { + // lock the state for the entire block mining process + let mut state = self.state.write().await; - let Some(mut pending) = pending else { - warn!("No pending block to mine"); - return; + let partial_header = PartialHeader { + gas_price: self.env.read().block.gas_price, + number: self.env.read().block.block_number.0, + timestamp: self.env.read().block.block_timestamp.0, + parent_hash: self.blockchain.storage.read().latest_hash, + sequencer_address: (*self.env.read().block.sequencer_address.0.key()).into(), }; - let block = { - let partial_block = pending.as_block(); - Block::new(partial_block.header, partial_block.transactions, partial_block.outputs) - }; + let (valid_txs, outputs): (Vec<_>, Vec<_>) = execution_outcome + .transactions + .iter() + .filter_map(|tx| match tx { + MaybeInvalidExecutedTransaction::Valid(tx) => Some((tx.clone(), tx.output.clone())), + _ => None, + }) + .unzip(); + + let block = Block::new(partial_header, valid_txs, outputs); - let block_hash = block.header.hash(); let block_number = block.header.number; let tx_count = block.transactions.len(); + let block_hash = block.header.hash(); - // Stores the pending transaction in storage - for tx in &block.transactions { - let transaction_hash = tx.inner.hash(); - self.storage.write().await.transactions.insert( - transaction_hash, - IncludedTransaction { - block_number, - block_hash, - transaction: tx.clone(), - status: TransactionStatus::AcceptedOnL2, + execution_outcome.transactions.iter().for_each(|tx| { + let (hash, tx) = match tx { + MaybeInvalidExecutedTransaction::Valid(tx) => ( + tx.inner.hash(), + KnownTransaction::Included(IncludedTransaction { + block_number, + block_hash, + transaction: tx.clone(), + finality_status: TransactionFinalityStatus::AcceptedOnL2, + }), + ), + + MaybeInvalidExecutedTransaction::Invalid(tx) => { + (tx.inner.hash(), KnownTransaction::Rejected(tx.clone())) } - .into(), - ); - } + }; - // get state diffs - let pending_state_diff = pending.state.to_state_diff(); - let state_diff = convert_state_diff_to_rpc_state_diff(pending_state_diff); + self.blockchain.storage.write().transactions.insert(hash, tx); + }); // store block and the state diff - self.storage.write().await.append_block(block_hash, block, state_diff); - - info!("⛏️ Block {block_number} mined with {tx_count} transactions"); - - // TODO: This is a hack, we should be able to apply the state diff directly - // to the current state instead of applying the diff onto the pending state. - // This is because `CachedState` have no notion of `Sierra` class, which whenever - // we execute a Declare transaction, we will set the `Sierra` class directly to the - // underlying `MemDb`. However, this is not reflected in the `CachedState`. - // - // Set the new pending state as the current state - let mut new_state = pending.state.state.clone(); - new_state.apply_state(&mut pending.state); - *self.state.write().await = new_state.clone(); - + let state_diff = convert_state_diff_to_rpc_state_diff(execution_outcome.state_diff.clone()); + self.blockchain.append_block(block_hash, block.clone(), state_diff); + // apply the pending state to the current state + execution_outcome.apply_to(&mut *state); // store the current state - self.states.write().await.insert(block_hash, new_state); - } - - pub async fn open_pending_block(&self) { - let latest_hash = self.storage.read().await.latest_hash; - let latest_state = self.state.read().await.clone(); + self.states.write().await.insert(block_hash, state.as_ref_db()); - self.update_block_context(); + info!(target: "backend", "⛏️ Block {block_number} mined with {tx_count} transactions"); - let _ = self.pending_block.write().await.insert(PendingBlockExecutor::new( - latest_hash, - latest_state, - self.env.clone(), - self.storage.clone(), - )); + MinedBlockOutcome { block_number, transactions: execution_outcome.transactions } } - fn update_block_context(&self) { + pub fn update_block_context(&self) { let mut context_gen = self.block_context_generator.write(); let block_context = &mut self.env.write().block; - let current_timestamp_secs = get_current_timestamp().as_secs() as i64; let timestamp = if context_gen.next_block_start_time == 0 { @@ -263,7 +316,7 @@ impl Backend { pub fn call( &self, call: ExternalFunctionCall, - state: MemDb, + state: impl StateReader, ) -> Result { let mut state = CachedState::new(state); let mut state = CachedState::new(MutRefState::new(&mut state)); @@ -287,28 +340,14 @@ impl Backend { ); if let Err(err) = &res { - warn!("Call error: {err:?}"); + warn!(target: "backend", "Call error: {err:?}"); } res } - pub async fn pending_state(&self) -> Option { - let Some(ref mut block) = *self.pending_block.write().await else { - return None; - }; - - let mut current_state = self.state.read().await.clone(); - current_state.apply_state(&mut block.state); - Some(current_state) - } - - pub async fn latest_state(&self) -> MemDb { - self.state.read().await.clone() - } - pub async fn create_empty_block(&self) -> Block { - let parent_hash = self.storage.read().await.latest_hash; + let parent_hash = self.blockchain.storage.read().latest_hash; let block_context = &self.env.read().block; let partial_header = PartialHeader { @@ -321,46 +360,4 @@ impl Backend { Block::new(partial_header, vec![], vec![]) } - - pub async fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { - if self.has_pending_transactions().await { - return Err(SequencerError::PendingTransactions); - } - self.block_context_generator.write().next_block_start_time = timestamp; - Ok(()) - } - - pub async fn increase_next_block_timestamp( - &self, - timestamp: u64, - ) -> Result<(), SequencerError> { - if self.has_pending_transactions().await { - return Err(SequencerError::PendingTransactions); - } - self.block_context_generator.write().block_timestamp_offset += timestamp as i64; - Ok(()) - } - - async fn has_pending_transactions(&self) -> bool { - if let Some(ref block) = *self.pending_block.read().await { - !block.transactions.is_empty() - } else { - false - } - } - - pub async fn set_storage_at( - &self, - contract_address: ContractAddress, - storage_key: StorageKey, - value: StarkFelt, - ) -> Result<(), SequencerError> { - match self.pending_block.write().await.as_mut() { - Some(pending_block) => { - pending_block.state.set_storage_at(contract_address, storage_key, value); - Ok(()) - } - None => Err(SequencerError::StateNotFound(BlockId::Tag(BlockTag::Pending))), - } - } } diff --git a/crates/katana/core/src/backend/state.rs b/crates/katana/core/src/backend/state.rs deleted file mode 100644 index b844d26a55..0000000000 --- a/crates/katana/core/src/backend/state.rs +++ /dev/null @@ -1,543 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use anyhow::Result; -use blockifier::execution::contract_class::ContractClass; -use blockifier::state::cached_state::CommitmentStateDiff; -use blockifier::state::errors::StateError; -use blockifier::state::state_api::{State, StateReader, StateResult}; -use starknet::core::types::FlattenedSierraClass; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::patricia_key; -use starknet_api::state::StorageKey; - -use crate::constants::{ - ERC20_CONTRACT, ERC20_CONTRACT_CLASS_HASH, FEE_TOKEN_ADDRESS, UDC_ADDRESS, UDC_CLASS_HASH, - UDC_CONTRACT, -}; -use crate::db::serde::state::{ - SerializableClassRecord, SerializableState, SerializableStorageRecord, -}; -use crate::db::Db; - -pub trait StateExt { - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()>; - - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult; - - fn apply_state(&mut self, state: &mut S) - where - S: State + StateReader; -} - -#[derive(Clone, Debug, Default)] -pub struct StorageRecord { - pub nonce: Nonce, - pub class_hash: ClassHash, - pub storage: HashMap, -} - -#[derive(Clone, Debug)] -pub struct ClassRecord { - /// The compiled contract class. - pub class: ContractClass, - /// The hash of a compiled Sierra class (if the class is a Sierra class, otherwise - /// for legacy contract, it is the same as the class hash). - pub compiled_hash: CompiledClassHash, -} - -#[derive(Clone, Debug)] -pub struct MemDb { - /// A map of class hash to its class definition. - pub classes: HashMap, - /// A map of contract address to the contract information. - pub storage: HashMap, - /// A map of class hash to its Sierra class definition (if any). - pub sierra_classes: HashMap, -} - -impl Default for MemDb { - fn default() -> Self { - let mut state = MemDb { - storage: HashMap::new(), - classes: HashMap::new(), - sierra_classes: HashMap::new(), - }; - deploy_fee_contract(&mut state); - deploy_universal_deployer_contract(&mut state); - state - } -} - -impl StateExt for MemDb { - fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { - if let ContractClass::V0(_) = self.get_compiled_contract_class(class_hash)? { - return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); - }; - - self.sierra_classes - .get(class_hash) - .cloned() - .ok_or(StateError::StateReadError("Missing Sierra class".to_string())) - } - - fn set_sierra_class( - &mut self, - class_hash: ClassHash, - sierra_class: FlattenedSierraClass, - ) -> StateResult<()> { - // check the class hash must not be a legacy contract - if let ContractClass::V0(_) = self.get_compiled_contract_class(&class_hash)? { - return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); - }; - self.sierra_classes.insert(class_hash, sierra_class); - Ok(()) - } - - fn apply_state(&mut self, state: &mut S) - where - S: State + StateReader, - { - // Generate the state diff - let state_diff = state.to_state_diff(); - - // update contract storages - state_diff.storage_updates.into_iter().for_each(|(contract_address, storages)| { - storages.into_iter().for_each(|(key, value)| { - self.set_storage_at(contract_address, key, value); - }) - }); - - // update declared contracts - // apply newly declared classses - for (class_hash, compiled_class_hash) in &state_diff.class_hash_to_compiled_class_hash { - let contract_class = - state.get_compiled_contract_class(class_hash).expect("contract class should exist"); - self.set_contract_class(class_hash, contract_class).unwrap(); - self.set_compiled_class_hash(*class_hash, *compiled_class_hash).unwrap(); - } - - // update deployed contracts - state_diff.address_to_class_hash.into_iter().for_each(|(contract_address, class_hash)| { - self.set_class_hash_at(contract_address, class_hash).unwrap() - }); - - // update accounts nonce - state_diff.address_to_nonce.into_iter().for_each(|(contract_address, nonce)| { - if let Some(r) = self.storage.get_mut(&contract_address) { - r.nonce = nonce; - } - }); - } -} - -impl State for MemDb { - fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { - let current_nonce = self.get_nonce_at(contract_address)?; - let current_nonce_as_u64 = usize::try_from(current_nonce.0)? as u64; - let next_nonce_val = 1_u64 + current_nonce_as_u64; - let next_nonce = Nonce(StarkFelt::from(next_nonce_val)); - self.storage.entry(contract_address).or_default().nonce = next_nonce; - Ok(()) - } - - fn set_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - value: StarkFelt, - ) { - self.storage.entry(contract_address).or_default().storage.insert(key, value); - } - - fn set_class_hash_at( - &mut self, - contract_address: ContractAddress, - class_hash: ClassHash, - ) -> StateResult<()> { - if contract_address == ContractAddress::default() { - return Err(StateError::OutOfRangeContractAddress); - } - self.storage.entry(contract_address).or_default().class_hash = class_hash; - Ok(()) - } - - fn set_compiled_class_hash( - &mut self, - class_hash: ClassHash, - compiled_class_hash: CompiledClassHash, - ) -> StateResult<()> { - if !self.classes.contains_key(&class_hash) { - return Err(StateError::UndeclaredClassHash(class_hash)); - } - self.classes.entry(class_hash).and_modify(|r| r.compiled_hash = compiled_class_hash); - Ok(()) - } - - fn set_contract_class( - &mut self, - class_hash: &ClassHash, - contract_class: ContractClass, - ) -> StateResult<()> { - let compiled_hash = CompiledClassHash(class_hash.0); - self.classes.insert(*class_hash, ClassRecord { class: contract_class, compiled_hash }); - Ok(()) - } - - fn to_state_diff(&self) -> CommitmentStateDiff { - CommitmentStateDiff { - storage_updates: [].into(), - address_to_nonce: [].into(), - address_to_class_hash: [].into(), - class_hash_to_compiled_class_hash: [].into(), - } - } -} - -impl StateReader for MemDb { - fn get_storage_at( - &mut self, - contract_address: ContractAddress, - key: StorageKey, - ) -> StateResult { - let value = self - .storage - .get(&contract_address) - .and_then(|r| r.storage.get(&key)) - .copied() - .unwrap_or_default(); - Ok(value) - } - - fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { - let nonce = self.storage.get(&contract_address).map(|r| r.nonce).unwrap_or_default(); - Ok(nonce) - } - - fn get_compiled_contract_class( - &mut self, - class_hash: &ClassHash, - ) -> StateResult { - self.classes - .get(class_hash) - .map(|r| r.class.clone()) - .ok_or(StateError::UndeclaredClassHash(*class_hash)) - } - - fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { - let class_hash = - self.storage.get(&contract_address).map(|r| r.class_hash).unwrap_or_default(); - Ok(class_hash) - } - - fn get_compiled_class_hash( - &mut self, - class_hash: ClassHash, - ) -> StateResult { - self.classes - .get(&class_hash) - .map(|r| r.compiled_hash) - .ok_or(StateError::UndeclaredClassHash(class_hash)) - } -} - -impl Db for MemDb { - fn dump_state(&self) -> Result { - let mut serializable = SerializableState::default(); - - self.storage.iter().for_each(|(addr, storage)| { - let mut record = SerializableStorageRecord { - storage: BTreeMap::new(), - nonce: storage.nonce.0.into(), - class_hash: storage.class_hash.0.into(), - }; - - storage.storage.iter().for_each(|(key, value)| { - record.storage.insert((*key.0.key()).into(), (*value).into()); - }); - - serializable.storage.insert((*addr.0.key()).into(), record); - }); - - self.classes.iter().for_each(|(class_hash, class_record)| { - serializable.classes.insert( - class_hash.0.into(), - SerializableClassRecord { - class: class_record.class.clone().into(), - compiled_hash: class_record.compiled_hash.0.into(), - }, - ); - }); - - self.sierra_classes.iter().for_each(|(class_hash, class)| { - serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); - }); - - Ok(serializable) - } - - fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { - self.storage.entry(addr).or_default().nonce = nonce; - } -} - -fn deploy_fee_contract(state: &mut MemDb) { - let address = ContractAddress(patricia_key!(*FEE_TOKEN_ADDRESS)); - let hash = ClassHash(*ERC20_CONTRACT_CLASS_HASH); - let compiled_hash = CompiledClassHash(*ERC20_CONTRACT_CLASS_HASH); - - state.classes.insert(hash, ClassRecord { class: (*ERC20_CONTRACT).clone(), compiled_hash }); - state.storage.insert( - address, - StorageRecord { class_hash: hash, nonce: Nonce(1_u128.into()), storage: HashMap::new() }, - ); -} - -fn deploy_universal_deployer_contract(state: &mut MemDb) { - let address = ContractAddress(patricia_key!(*UDC_ADDRESS)); - let hash = ClassHash(*UDC_CLASS_HASH); - let compiled_hash = CompiledClassHash(*UDC_CLASS_HASH); - - state.classes.insert(hash, ClassRecord { class: (*UDC_CONTRACT).clone(), compiled_hash }); - state.storage.insert( - address, - StorageRecord { class_hash: hash, nonce: Nonce(1_u128.into()), storage: HashMap::new() }, - ); -} - -/// Unit tests ported from `blockifier`. -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use blockifier::state::cached_state::CachedState; - use starknet_api::core::PatriciaKey; - use starknet_api::stark_felt; - - use super::*; - - #[test] - fn get_uninitialized_storage_value() { - let mut state = CachedState::new(MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }); - let contract_address = ContractAddress(patricia_key!("0x1")); - let key = StorageKey(patricia_key!("0x10")); - assert_eq!(state.get_storage_at(contract_address, key).unwrap(), StarkFelt::default()); - } - - #[test] - fn get_and_set_storage_value() { - let contract_address0 = ContractAddress(patricia_key!("0x100")); - let contract_address1 = ContractAddress(patricia_key!("0x200")); - let key0 = StorageKey(patricia_key!("0x10")); - let key1 = StorageKey(patricia_key!("0x20")); - let storage_val0 = stark_felt!("0x1"); - let storage_val1 = stark_felt!("0x5"); - - let mut state = CachedState::new(MemDb { - storage: HashMap::from([ - ( - contract_address0, - StorageRecord { - class_hash: ClassHash(0_u32.into()), - nonce: Nonce(0_u32.into()), - storage: HashMap::from([(key0, storage_val0)]), - }, - ), - ( - contract_address1, - StorageRecord { - class_hash: ClassHash(0_u32.into()), - nonce: Nonce(0_u32.into()), - storage: HashMap::from([(key1, storage_val1)]), - }, - ), - ]), - classes: HashMap::new(), - sierra_classes: HashMap::new(), - }); - - assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), storage_val0); - assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), storage_val1); - - let modified_storage_value0 = stark_felt!("0xA"); - state.set_storage_at(contract_address0, key0, modified_storage_value0); - assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), modified_storage_value0); - assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), storage_val1); - - let modified_storage_value1 = stark_felt!("0x7"); - state.set_storage_at(contract_address1, key1, modified_storage_value1); - assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), modified_storage_value0); - assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), modified_storage_value1); - } - - #[test] - fn get_uninitialized_value() { - let mut state = CachedState::new(MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }); - let contract_address = ContractAddress(patricia_key!("0x1")); - assert_eq!(state.get_nonce_at(contract_address).unwrap(), Nonce::default()); - } - - #[test] - fn get_uninitialized_class_hash_value() { - let mut state = CachedState::new(MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }); - let valid_contract_address = ContractAddress(patricia_key!("0x1")); - assert_eq!(state.get_class_hash_at(valid_contract_address).unwrap(), ClassHash::default()); - } - - #[test] - fn cannot_set_class_hash_to_uninitialized_contract() { - let mut state = CachedState::new(MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }); - let uninitialized_contract_address = ContractAddress::default(); - let class_hash = ClassHash(stark_felt!("0x100")); - assert_matches!( - state.set_class_hash_at(uninitialized_contract_address, class_hash).unwrap_err(), - StateError::OutOfRangeContractAddress - ); - } - - #[test] - fn get_and_increment_nonce() { - let contract_address1 = ContractAddress(patricia_key!("0x100")); - let contract_address2 = ContractAddress(patricia_key!("0x200")); - let initial_nonce = Nonce(stark_felt!("0x1")); - - let mut state = CachedState::new(MemDb { - storage: HashMap::from([ - ( - contract_address1, - StorageRecord { - class_hash: ClassHash(0_u32.into()), - nonce: initial_nonce, - storage: HashMap::new(), - }, - ), - ( - contract_address2, - StorageRecord { - class_hash: ClassHash(0_u32.into()), - nonce: initial_nonce, - storage: HashMap::new(), - }, - ), - ]), - classes: HashMap::new(), - sierra_classes: HashMap::new(), - }); - - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), initial_nonce); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); - - assert!(state.increment_nonce(contract_address1).is_ok()); - let nonce1_plus_one = Nonce(stark_felt!("0x2")); - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_one); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); - - assert!(state.increment_nonce(contract_address1).is_ok()); - let nonce1_plus_two = Nonce(stark_felt!("0x3")); - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_two); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); - - assert!(state.increment_nonce(contract_address2).is_ok()); - let nonce2_plus_one = Nonce(stark_felt!("0x2")); - assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_two); - assert_eq!(state.get_nonce_at(contract_address2).unwrap(), nonce2_plus_one); - } - - #[test] - fn apply_state_update() { - let mut old_state = MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }; - let mut new_state = CachedState::new(MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }); - - let class_hash = ClassHash(stark_felt!("0x1")); - let address = ContractAddress(patricia_key!("0x1")); - let storage_key = StorageKey(patricia_key!("0x77")); - let storage_val = stark_felt!("0x66"); - let contract = (*UDC_CONTRACT).clone(); - let compiled_hash = CompiledClassHash(class_hash.0); - - new_state.set_contract_class(&class_hash, (*UDC_CONTRACT).clone()).unwrap(); - new_state.set_compiled_class_hash(class_hash, CompiledClassHash(class_hash.0)).unwrap(); - new_state.set_class_hash_at(address, class_hash).unwrap(); - new_state.set_storage_at(address, storage_key, storage_val); - - assert_matches!( - old_state.get_compiled_contract_class(&class_hash), - Err(StateError::UndeclaredClassHash(_)) - ); - assert_matches!( - old_state.get_compiled_class_hash(class_hash), - Err(StateError::UndeclaredClassHash(_)) - ); - assert_eq!(old_state.get_class_hash_at(address).unwrap(), ClassHash::default()); - assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), StarkFelt::default()); - - old_state.apply_state(&mut new_state); - - assert_eq!(old_state.get_compiled_contract_class(&class_hash).unwrap(), contract); - assert_eq!(old_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); - assert_eq!(old_state.get_class_hash_at(address).unwrap(), class_hash); - assert_eq!(old_state.get_storage_at(address, storage_key).unwrap(), storage_val); - } - - #[test] - fn dump_and_load_state() { - let mut state = MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }; - - let class_hash = ClassHash(stark_felt!("0x1")); - let address = ContractAddress(patricia_key!("0x1")); - let storage_key = StorageKey(patricia_key!("0x77")); - let storage_val = stark_felt!("0x66"); - let contract = (*UDC_CONTRACT).clone(); - let compiled_hash = CompiledClassHash(class_hash.0); - - state.set_contract_class(&class_hash, (*UDC_CONTRACT).clone()).unwrap(); - state.set_compiled_class_hash(class_hash, CompiledClassHash(class_hash.0)).unwrap(); - state.set_class_hash_at(address, class_hash).unwrap(); - state.set_storage_at(address, storage_key, storage_val); - - let dump = state.dump_state().expect("should dump state"); - - let mut new_state = MemDb { - classes: HashMap::new(), - storage: HashMap::new(), - sierra_classes: HashMap::new(), - }; - new_state.load_state(dump).expect("should load state"); - - assert_eq!(new_state.get_compiled_contract_class(&class_hash).unwrap(), contract); - assert_eq!(new_state.get_compiled_class_hash(class_hash).unwrap(), compiled_hash); - assert_eq!(new_state.get_class_hash_at(address).unwrap(), class_hash); - assert_eq!(new_state.get_storage_at(address, storage_key).unwrap(), storage_val); - } -} diff --git a/crates/katana/core/src/backend/storage/block.rs b/crates/katana/core/src/backend/storage/block.rs index 5cab98fa78..4d16aadc0c 100644 --- a/crates/katana/core/src/backend/storage/block.rs +++ b/crates/katana/core/src/backend/storage/block.rs @@ -8,7 +8,7 @@ use starknet::core::types::{ }; use super::transaction::TransactionOutput; -use crate::backend::executor::ExecutedTransaction; +use crate::execution::ExecutedTransaction; use crate::utils::transaction::api_to_rpc_transaction; #[derive(Debug, Clone, Copy)] diff --git a/crates/katana/core/src/backend/storage/mod.rs b/crates/katana/core/src/backend/storage/mod.rs index 3e050de240..a166103da3 100644 --- a/crates/katana/core/src/backend/storage/mod.rs +++ b/crates/katana/core/src/backend/storage/mod.rs @@ -1,12 +1,14 @@ use std::collections::{HashMap, VecDeque}; +use std::sync::Arc; use blockifier::block_context::BlockContext; +use parking_lot::RwLock; use starknet::core::types::{BlockId, BlockTag, FieldElement, StateDiff, StateUpdate}; use self::block::Block; use self::transaction::KnownTransaction; -use super::state::MemDb; use crate::backend::storage::block::PartialHeader; +use crate::db::StateRefDb; pub mod block; pub mod transaction; @@ -17,7 +19,7 @@ const MIN_HISTORY_LIMIT: usize = 10; /// Represents the complete state of a single block pub struct InMemoryBlockStates { /// The states at a certain block - states: HashMap, + states: HashMap, /// How many states to store at most in_memory_limit: usize, /// minimum amount of states we keep in memory @@ -37,7 +39,7 @@ impl InMemoryBlockStates { } /// Returns the state for the given `hash` if present - pub fn get(&self, hash: &FieldElement) -> Option<&MemDb> { + pub fn get(&self, hash: &FieldElement) -> Option<&StateRefDb> { self.states.get(hash) } @@ -49,7 +51,7 @@ impl InMemoryBlockStates { /// Since we keep a snapshot of the entire state as history, the size of the state will increase /// with the transactions processed. To counter this, we gradually decrease the cache limit with /// the number of states/blocks until we reached the `min_limit`. - pub fn insert(&mut self, hash: FieldElement, state: MemDb) { + pub fn insert(&mut self, hash: FieldElement, state: StateRefDb) { if self.present.len() >= self.in_memory_limit { // once we hit the max limit we gradually decrease it self.in_memory_limit = @@ -80,9 +82,8 @@ impl Default for InMemoryBlockStates { } } -// TODO: can we wrap all the fields in a `RwLock` to prevent read blocking? #[derive(Debug, Default)] -pub struct BlockchainStorage { +pub struct Storage { /// Mapping from block hash -> block pub blocks: HashMap, /// Mapping from block number -> block hash @@ -97,7 +98,7 @@ pub struct BlockchainStorage { pub transactions: HashMap, } -impl BlockchainStorage { +impl Storage { /// Creates a new blockchain from a genesis block pub fn new(block_context: &BlockContext) -> Self { let partial_header = PartialHeader { @@ -123,44 +124,75 @@ impl BlockchainStorage { } } - /// Appends a new block to the chain and store the state diff. - pub fn append_block(&mut self, hash: FieldElement, block: Block, state_diff: StateDiff) { - let number = block.header.number; - - assert_eq!(self.latest_number + 1, number); + /// Creates a new blockchain from a forked network + pub fn new_forked(latest_number: u64, latest_hash: FieldElement) -> Self { + Self { + latest_hash, + latest_number, + blocks: HashMap::default(), + hashes: HashMap::from([(latest_number, latest_hash)]), + state_update: HashMap::default(), + transactions: HashMap::default(), + } + } - let old_root = self.blocks.get(&self.latest_hash).map(|b| b.header.state_root); + pub fn block_by_number(&self, number: u64) -> Option<&Block> { + self.hashes.get(&number).and_then(|hash| self.blocks.get(hash)) + } +} - let state_update = StateUpdate { - block_hash: hash, - new_root: block.header.state_root, - old_root: if number == 0 { FieldElement::ZERO } else { old_root.unwrap() }, - state_diff, - }; +pub struct Blockchain { + pub storage: Arc>, +} - self.latest_hash = hash; - self.latest_number = number; - self.blocks.insert(hash, block); - self.hashes.insert(number, hash); - self.state_update.insert(hash, state_update); +impl Blockchain { + pub fn new(storage: Arc>) -> Self { + Self { storage } } - pub fn total_blocks(&self) -> usize { - self.blocks.len() + pub fn new_forked(latest_number: u64, latest_hash: FieldElement) -> Self { + Self::new(Arc::new(RwLock::new(Storage::new_forked(latest_number, latest_hash)))) } /// Returns the block hash based on the block id pub fn block_hash(&self, block: BlockId) -> Option { match block { BlockId::Tag(BlockTag::Pending) => None, - BlockId::Tag(BlockTag::Latest) => Some(self.latest_hash), + BlockId::Tag(BlockTag::Latest) => Some(self.storage.read().latest_hash), BlockId::Hash(hash) => Some(hash), - BlockId::Number(num) => self.hashes.get(&num).copied(), + BlockId::Number(num) => self.storage.read().hashes.get(&num).copied(), } } - pub fn block_by_number(&self, number: u64) -> Option<&Block> { - self.hashes.get(&number).and_then(|hash| self.blocks.get(hash)) + pub fn total_blocks(&self) -> usize { + self.storage.read().blocks.len() + } + + /// Appends a new block to the chain and store the state diff. + pub fn append_block(&self, hash: FieldElement, block: Block, state_diff: StateDiff) { + let number = block.header.number; + let mut storage = self.storage.write(); + + assert_eq!(storage.latest_number + 1, number); + + let old_root = storage + .blocks + .get(&storage.latest_hash) + .map(|b| b.header.state_root) + .unwrap_or_default(); + + let state_update = StateUpdate { + block_hash: hash, + new_root: block.header.state_root, + old_root, + state_diff, + }; + + storage.latest_hash = hash; + storage.latest_number = number; + storage.blocks.insert(hash, block); + storage.hashes.insert(number, hash); + storage.state_update.insert(hash, state_update); } } @@ -169,18 +201,23 @@ mod tests { use std::str::FromStr; use super::*; + use crate::backend::in_memory_db::MemDb; #[test] fn remove_old_state_when_limit_is_reached() { let mut in_memory_state = InMemoryBlockStates::new(2); - in_memory_state.insert(FieldElement::from_str("0x1").unwrap(), MemDb::default()); - in_memory_state.insert(FieldElement::from_str("0x2").unwrap(), MemDb::default()); + in_memory_state + .insert(FieldElement::from_str("0x1").unwrap(), StateRefDb::new(MemDb::new())); + in_memory_state + .insert(FieldElement::from_str("0x2").unwrap(), StateRefDb::new(MemDb::new())); + assert!(in_memory_state.states.get(&FieldElement::from_str("0x1").unwrap()).is_some()); assert!(in_memory_state.states.get(&FieldElement::from_str("0x2").unwrap()).is_some()); assert_eq!(in_memory_state.present.len(), 2); - in_memory_state.insert(FieldElement::from_str("0x3").unwrap(), MemDb::default()); + in_memory_state + .insert(FieldElement::from_str("0x3").unwrap(), StateRefDb::new(MemDb::new())); assert_eq!(in_memory_state.present.len(), 2); assert!(in_memory_state.states.get(&FieldElement::from_str("0x1").unwrap()).is_none()); diff --git a/crates/katana/core/src/backend/storage/transaction.rs b/crates/katana/core/src/backend/storage/transaction.rs index ee87b9fcee..dbdf27a88c 100644 --- a/crates/katana/core/src/backend/storage/transaction.rs +++ b/crates/katana/core/src/backend/storage/transaction.rs @@ -2,50 +2,39 @@ use std::sync::Arc; use blockifier::execution::contract_class::ContractClass; use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::transaction_execution::Transaction as ExecutionTransaction; use blockifier::transaction::transactions::{ DeclareTransaction as ExecutionDeclareTransaction, DeployAccountTransaction as ExecutionDeployAccountTransaction, + L1HandlerTransaction as ExecutionL1HandlerTransaction, }; use starknet::core::types::{ DeclareTransactionReceipt, DeployAccountTransactionReceipt, Event, FieldElement, - FlattenedSierraClass, InvokeTransactionReceipt, MsgToL1, PendingDeclareTransactionReceipt, - PendingDeployAccountTransactionReceipt, PendingInvokeTransactionReceipt, + FlattenedSierraClass, InvokeTransactionReceipt, L1HandlerTransactionReceipt, MsgToL1, + PendingDeclareTransactionReceipt, PendingDeployAccountTransactionReceipt, + PendingInvokeTransactionReceipt, PendingL1HandlerTransactionReceipt, PendingTransactionReceipt as RpcPendingTransactionReceipt, Transaction as RpcTransaction, - TransactionReceipt as RpcTransactionReceipt, TransactionStatus as RpcTransactionStatus, + TransactionFinalityStatus, TransactionReceipt as RpcTransactionReceipt, }; use starknet_api::core::{ContractAddress, PatriciaKey}; use starknet_api::hash::StarkHash; use starknet_api::patricia_key; use starknet_api::transaction::{ DeclareTransaction as ApiDeclareTransaction, - DeployAccountTransaction as ApiDeployAccountTransaction, - InvokeTransaction as ApiInvokeTransaction, Transaction as ApiTransaction, + DeployAccountTransaction as ApiDeployAccountTransaction, Fee, + InvokeTransaction as ApiInvokeTransaction, L1HandlerTransaction as ApiL1HandlerTransaction, + Transaction as ApiTransaction, }; -use crate::backend::executor::ExecutedTransaction; +use crate::execution::ExecutedTransaction; use crate::utils::transaction::api_to_rpc_transaction; -/// The status of the transactions known to the sequencer. -#[derive(Debug, Clone, Copy)] -pub enum TransactionStatus { - /// Transaction executed unsuccessfully and thus was skipped. - Rejected, - /// When the transaction pass validation but encountered error during execution. - Reverted, - /// Transactions that have been included in the L2 block which have - /// passed both validation and execution. - AcceptedOnL2, - /// When the block of which the transaction is included in have been committed to the - /// L1 settlement layer. - AcceptedOnL1, -} - /// Represents all transactions that are known to the sequencer. #[derive(Debug, Clone)] pub enum KnownTransaction { Pending(PendingTransaction), Included(IncludedTransaction), - Rejected(Box), + Rejected(Arc), } impl KnownTransaction { @@ -72,14 +61,14 @@ pub struct IncludedTransaction { pub block_number: u64, pub block_hash: FieldElement, pub transaction: Arc, - pub status: TransactionStatus, + pub finality_status: TransactionFinalityStatus, } /// A transaction that is known to be rejected by the sequencer i.e., /// transaction that didn't pass the validation logic. #[derive(Debug, Clone)] pub struct RejectedTransaction { - pub transaction: ApiTransaction, + pub inner: Transaction, pub execution_error: String, } @@ -95,6 +84,7 @@ pub enum Transaction { Invoke(InvokeTransaction), Declare(DeclareTransaction), DeployAccount(DeployAccountTransaction), + L1Handler(L1HandlerTransaction), } impl Transaction { @@ -102,6 +92,7 @@ impl Transaction { match self { Transaction::Invoke(tx) => tx.0.transaction_hash().0.into(), Transaction::Declare(tx) => tx.inner.transaction_hash().0.into(), + Transaction::L1Handler(tx) => tx.inner.transaction_hash.0.into(), Transaction::DeployAccount(tx) => tx.inner.transaction_hash.0.into(), } } @@ -123,37 +114,59 @@ pub struct DeployAccountTransaction { pub contract_address: FieldElement, } +#[derive(Debug, Clone)] +pub struct L1HandlerTransaction { + pub inner: ApiL1HandlerTransaction, + pub paid_l1_fee: u128, +} + impl IncludedTransaction { pub fn receipt(&self) -> RpcTransactionReceipt { match &self.transaction.inner { Transaction::Invoke(_) => RpcTransactionReceipt::Invoke(InvokeTransactionReceipt { - status: self.status.into(), block_hash: self.block_hash, block_number: self.block_number, + finality_status: self.finality_status, events: self.transaction.output.events.clone(), + execution_result: self.transaction.execution_result(), messages_sent: self.transaction.output.messages_sent.clone(), transaction_hash: self.transaction.inner.hash(), actual_fee: self.transaction.execution_info.actual_fee.0.into(), }), Transaction::Declare(_) => RpcTransactionReceipt::Declare(DeclareTransactionReceipt { - status: self.status.into(), block_hash: self.block_hash, block_number: self.block_number, + finality_status: self.finality_status, events: self.transaction.output.events.clone(), transaction_hash: self.transaction.inner.hash(), + execution_result: self.transaction.execution_result(), messages_sent: self.transaction.output.messages_sent.clone(), actual_fee: self.transaction.execution_info.actual_fee.0.into(), }), Transaction::DeployAccount(tx) => { RpcTransactionReceipt::DeployAccount(DeployAccountTransactionReceipt { - status: self.status.into(), block_hash: self.block_hash, block_number: self.block_number, contract_address: tx.contract_address, + finality_status: self.finality_status, events: self.transaction.output.events.clone(), transaction_hash: self.transaction.inner.hash(), + execution_result: self.transaction.execution_result(), + messages_sent: self.transaction.output.messages_sent.clone(), + actual_fee: self.transaction.execution_info.actual_fee.0.into(), + }) + } + + Transaction::L1Handler(_) => { + RpcTransactionReceipt::L1Handler(L1HandlerTransactionReceipt { + block_hash: self.block_hash, + block_number: self.block_number, + finality_status: self.finality_status, + events: self.transaction.output.events.clone(), + transaction_hash: self.transaction.inner.hash(), + execution_result: self.transaction.execution_result(), messages_sent: self.transaction.output.messages_sent.clone(), actual_fee: self.transaction.execution_info.actual_fee.0.into(), }) @@ -169,6 +182,7 @@ impl PendingTransaction { RpcPendingTransactionReceipt::Invoke(PendingInvokeTransactionReceipt { events: self.0.output.events.clone(), transaction_hash: self.0.inner.hash(), + execution_result: self.0.execution_result(), messages_sent: self.0.output.messages_sent.clone(), actual_fee: self.0.execution_info.actual_fee.0.into(), }) @@ -178,6 +192,7 @@ impl PendingTransaction { RpcPendingTransactionReceipt::Declare(PendingDeclareTransactionReceipt { events: self.0.output.events.clone(), transaction_hash: self.0.inner.hash(), + execution_result: self.0.execution_result(), messages_sent: self.0.output.messages_sent.clone(), actual_fee: self.0.execution_info.actual_fee.0.into(), }) @@ -187,32 +202,21 @@ impl PendingTransaction { PendingDeployAccountTransactionReceipt { events: self.0.output.events.clone(), transaction_hash: self.0.inner.hash(), + execution_result: self.0.execution_result(), messages_sent: self.0.output.messages_sent.clone(), actual_fee: self.0.execution_info.actual_fee.0.into(), }, ), - } - } -} - -impl KnownTransaction { - pub fn status(&self) -> TransactionStatus { - match self { - KnownTransaction::Pending(_) => TransactionStatus::AcceptedOnL2, - KnownTransaction::Rejected(_) => TransactionStatus::Rejected, - KnownTransaction::Included(tx) => tx.status, - } - } -} -impl From for RpcTransactionStatus { - fn from(status: TransactionStatus) -> Self { - match status { - TransactionStatus::AcceptedOnL2 => RpcTransactionStatus::AcceptedOnL2, - TransactionStatus::AcceptedOnL1 => RpcTransactionStatus::AcceptedOnL1, - TransactionStatus::Rejected => RpcTransactionStatus::Rejected, - // TODO: change this to `REVERTED` once the status is implemented in `starknet-rs` - TransactionStatus::Reverted => RpcTransactionStatus::AcceptedOnL2, + Transaction::L1Handler(_) => { + RpcPendingTransactionReceipt::L1Handler(PendingL1HandlerTransactionReceipt { + events: self.0.output.events.clone(), + transaction_hash: self.0.inner.hash(), + execution_result: self.0.execution_result(), + messages_sent: self.0.output.messages_sent.clone(), + actual_fee: self.0.execution_info.actual_fee.0.into(), + }) + } } } } @@ -231,7 +235,7 @@ impl From for KnownTransaction { impl From for KnownTransaction { fn from(transaction: RejectedTransaction) -> Self { - KnownTransaction::Rejected(Box::new(transaction)) + KnownTransaction::Rejected(Arc::new(transaction)) } } @@ -239,7 +243,7 @@ impl From for RpcTransaction { fn from(transaction: KnownTransaction) -> Self { match transaction { KnownTransaction::Pending(tx) => api_to_rpc_transaction(tx.0.inner.clone().into()), - KnownTransaction::Rejected(tx) => api_to_rpc_transaction(tx.transaction), + KnownTransaction::Rejected(tx) => api_to_rpc_transaction(tx.inner.clone().into()), KnownTransaction::Included(tx) => { api_to_rpc_transaction(tx.transaction.inner.clone().into()) } @@ -252,23 +256,37 @@ impl From for ApiTransaction { match value { Transaction::Invoke(tx) => ApiTransaction::Invoke(tx.0), Transaction::Declare(tx) => ApiTransaction::Declare(tx.inner), + Transaction::L1Handler(tx) => ApiTransaction::L1Handler(tx.inner), Transaction::DeployAccount(tx) => ApiTransaction::DeployAccount(tx.inner), } } } -impl From for AccountTransaction { +impl From for ExecutionTransaction { fn from(value: Transaction) -> Self { match value { - Transaction::Invoke(tx) => AccountTransaction::Invoke(tx.0), - Transaction::Declare(tx) => AccountTransaction::Declare( - ExecutionDeclareTransaction::new(tx.inner, tx.compiled_class) - .expect("declare tx must have valid compiled class"), - ), - Transaction::DeployAccount(tx) => { + Transaction::Invoke(tx) => { + ExecutionTransaction::AccountTransaction(AccountTransaction::Invoke(tx.0)) + } + + Transaction::Declare(tx) => { + ExecutionTransaction::AccountTransaction(AccountTransaction::Declare( + ExecutionDeclareTransaction::new(tx.inner, tx.compiled_class) + .expect("declare tx must have valid compiled class"), + )) + } + + Transaction::DeployAccount(tx) => ExecutionTransaction::AccountTransaction( AccountTransaction::DeployAccount(ExecutionDeployAccountTransaction { tx: tx.inner, contract_address: ContractAddress(patricia_key!(tx.contract_address)), + }), + ), + + Transaction::L1Handler(tx) => { + ExecutionTransaction::L1HandlerTransaction(ExecutionL1HandlerTransaction { + tx: tx.inner, + paid_fee_on_l1: Fee(tx.paid_l1_fee), }) } } diff --git a/crates/katana/core/src/db/cached.rs b/crates/katana/core/src/db/cached.rs new file mode 100644 index 0000000000..316d9bf32b --- /dev/null +++ b/crates/katana/core/src/db/cached.rs @@ -0,0 +1,598 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::cached_state::{CachedState, CommitmentStateDiff}; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::{State, StateReader, StateResult}; +use starknet::core::types::FlattenedSierraClass; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; +use tokio::sync::RwLock as AsyncRwLock; +use tracing::trace; + +use super::{AsStateRefDb, StateExt, StateExtRef, StateRefDb}; + +#[derive(Clone, Debug, Default)] +pub struct StorageRecord { + pub nonce: Nonce, + pub storage: HashMap, +} + +#[derive(Clone, Debug)] +pub struct ClassRecord { + /// The compiled contract class. + pub class: ContractClass, + /// The hash of a compiled Sierra class (if the class is a Sierra class, otherwise + /// for legacy contract, it is the same as the class hash). + pub compiled_hash: CompiledClassHash, +} + +/// A cached state database which fallbacks to an inner database if the data +/// is not found in the cache. +/// +/// The data that has been fetched from the inner database is stored in the cache. +#[derive(Clone, Debug)] +pub struct CachedDb { + /// A map of class hash to its class definition. + pub classes: HashMap, + /// A map of contract address to its class hash. + pub contracts: HashMap, + /// A map of contract address to the contract information. + pub storage: HashMap, + /// A map of class hash to its Sierra class definition (if any). + pub sierra_classes: HashMap, + /// Inner database to fallback to when the data is not found in the cache. + pub db: Db, +} + +impl CachedDb { + /// Construct a new [CachedDb] with an inner database. + pub fn new(db: Db) -> Self { + Self { + db, + classes: HashMap::new(), + storage: HashMap::new(), + contracts: HashMap::new(), + sierra_classes: HashMap::new(), + } + } +} + +impl State for CachedDb +where + Db: StateExtRef, +{ + fn set_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + value: StarkFelt, + ) { + self.storage.entry(contract_address).or_default().storage.insert(key, value); + } + + fn set_class_hash_at( + &mut self, + contract_address: ContractAddress, + class_hash: ClassHash, + ) -> StateResult<()> { + if contract_address == ContractAddress::default() { + return Err(StateError::OutOfRangeContractAddress); + } + self.contracts.insert(contract_address, class_hash); + Ok(()) + } + + fn set_compiled_class_hash( + &mut self, + class_hash: ClassHash, + compiled_class_hash: CompiledClassHash, + ) -> StateResult<()> { + self.classes.entry(class_hash).and_modify(|r| r.compiled_hash = compiled_class_hash); + Ok(()) + } + + fn set_contract_class( + &mut self, + class_hash: &ClassHash, + contract_class: ContractClass, + ) -> StateResult<()> { + self.classes.insert( + *class_hash, + ClassRecord { class: contract_class, compiled_hash: CompiledClassHash(class_hash.0) }, + ); + Ok(()) + } + + fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { + let current_nonce = if let Ok(nonce) = self.get_nonce_at(contract_address) { + nonce + } else { + self.db.get_nonce_at(contract_address)? + }; + + let current_nonce_as_u64 = usize::try_from(current_nonce.0)? as u64; + let next_nonce_val = 1_u64 + current_nonce_as_u64; + let next_nonce = Nonce(StarkFelt::from(next_nonce_val)); + self.storage.entry(contract_address).or_default().nonce = next_nonce; + Ok(()) + } + + fn to_state_diff(&self) -> CommitmentStateDiff { + unreachable!("to_state_diff should not be called on CachedDb") + } +} + +impl StateReader for CachedDb +where + Db: StateExtRef, +{ + fn get_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + if let Some(value) = self.storage.get(&contract_address).and_then(|r| r.storage.get(&key)) { + return Ok(*value); + } + + trace!(target: "cacheddb", "cache miss for storage at address {} index {}", contract_address.0.key(), key.0.key()); + + match self.db.get_storage_at(contract_address, key) { + Ok(value) => { + trace!(target: "cacheddb", "caching storage at address {} index {}", contract_address.0.key(), key.0.key()); + self.set_storage_at(contract_address, key, value); + Ok(value) + } + Err(err) => Err(err), + } + } + + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + if let Some(nonce) = self.storage.get(&contract_address).map(|r| r.nonce) { + return Ok(nonce); + } + + trace!(target: "cached_db", "cache miss for nonce at {}", contract_address.0.key()); + + match self.db.get_nonce_at(contract_address) { + Ok(nonce) => { + trace!(target: "cached_db", "caching nonce at {}", contract_address.0.key()); + self.storage.entry(contract_address).or_default().nonce = nonce; + Ok(nonce) + } + Err(err) => Err(err), + } + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + if let Some(class) = self.classes.get(class_hash).map(|r| r.class.clone()) { + return Ok(class); + } + + trace!(target: "cached_db", "cache miss for compiled contract class {class_hash}"); + + match self.db.get_compiled_contract_class(class_hash) { + Ok(class) => { + trace!(target: "cached_db", "caching compiled contract class {class_hash}"); + self.set_contract_class(class_hash, class.clone())?; + Ok(class) + } + Err(err) => Err(err), + } + } + + fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { + if let Some(class_hash) = self.contracts.get(&contract_address).cloned() { + return Ok(class_hash); + } + + trace!(target: "cached_db", "cache miss for class hash at address {}", contract_address.0.key()); + + match self.db.get_class_hash_at(contract_address) { + Ok(class_hash) => { + trace!(target: "cached_db", "caching class hash at address {}", contract_address.0.key()); + self.set_class_hash_at(contract_address, class_hash)?; + Ok(class_hash) + } + Err(err) => Err(err), + } + } + + fn get_compiled_class_hash( + &mut self, + class_hash: ClassHash, + ) -> StateResult { + if let Some(hash) = self.classes.get(&class_hash).map(|r| r.compiled_hash) { + return Ok(hash); + } + + trace!(target: "cached_db", "cache miss for compiled class hash {class_hash}"); + + match self.db.get_compiled_class_hash(class_hash) { + Ok(hash) => { + trace!(target: "cached_db", "caching compiled class hash {class_hash}",); + self.set_compiled_class_hash(class_hash, hash)?; + Ok(hash) + } + Err(err) => Err(err), + } + } +} + +impl StateExt for CachedDb +where + Db: StateExtRef, +{ + fn set_sierra_class( + &mut self, + class_hash: ClassHash, + sierra_class: FlattenedSierraClass, + ) -> StateResult<()> { + // check the class hash must not be a legacy contract + if let ContractClass::V0(_) = self.get_compiled_contract_class(&class_hash)? { + return Err(StateError::StateReadError("Class hash is not a Sierra class".to_string())); + }; + self.sierra_classes.insert(class_hash, sierra_class); + Ok(()) + } +} + +impl StateExtRef for CachedDb +where + Db: StateExtRef, +{ + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { + if let Some(class) = self.sierra_classes.get(class_hash).cloned() { + return Ok(class); + } + + trace!(target: "cached_db", "cache miss for sierra class {class_hash}"); + + match self.db.get_sierra_class(class_hash) { + Ok(class) => { + trace!(target: "cached_db", "caching sierra class {class_hash}"); + self.set_sierra_class(*class_hash, class.clone())?; + Ok(class) + } + Err(err) => Err(err), + } + } +} + +pub type AsCachedDb = CachedDb<()>; + +/// A helper trait for getting a [CachedDb] from a database implementation. +/// +/// **THIS IS JUST A TEMPORARY SOLUTION THEREFORE NOT MUCH THOUGHT HAS BEEN PUT INTO IT. DO NOT RELY +/// ON THIS TRAIT AS IT MAY BE REMOVED IN THE FUTURE**. +pub trait MaybeAsCachedDb { + /// Returns the inner [CachedDb] if it exists. Otherwise `None`. + fn maybe_as_cached_db(&self) -> Option; +} + +/// A wrapper type for [CachedState](blockifier::state::cached_state::CachedState) which +/// also allow storing the Sierra classes. +/// +/// The inner fields are wrapped in [Arc] and an async [RwLock](tokio::sync::RwLock) as to allow for +/// asynchronous access to the state. +/// +/// Example is when it is being referred to as a [StateRefDb] when the 'pending' state is being +/// requested while the block producer also have access to it in order to execute transactions and +/// produce blocks. +#[derive(Debug, Clone)] +pub struct CachedStateWrapper { + inner: Arc>>, + sierra_class: Arc>>, +} + +impl CachedStateWrapper +where + Db: StateExtRef, +{ + pub fn new(db: Db) -> Self { + Self { + sierra_class: Default::default(), + inner: Arc::new(AsyncRwLock::new(CachedState::new(db))), + } + } + + pub fn inner_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, CachedState> { + tokio::task::block_in_place(|| self.inner.blocking_write()) + } + + pub fn sierra_class( + &self, + ) -> tokio::sync::RwLockReadGuard<'_, HashMap> { + tokio::task::block_in_place(|| self.sierra_class.blocking_read()) + } + + pub fn sierra_class_mut( + &self, + ) -> tokio::sync::RwLockWriteGuard<'_, HashMap> { + tokio::task::block_in_place(|| self.sierra_class.blocking_write()) + } +} + +impl State for CachedStateWrapper +where + Db: StateExtRef, +{ + fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { + self.inner_mut().increment_nonce(contract_address) + } + + fn set_class_hash_at( + &mut self, + contract_address: ContractAddress, + class_hash: ClassHash, + ) -> StateResult<()> { + self.inner_mut().set_class_hash_at(contract_address, class_hash) + } + + fn set_compiled_class_hash( + &mut self, + class_hash: ClassHash, + compiled_class_hash: CompiledClassHash, + ) -> StateResult<()> { + self.inner_mut().set_compiled_class_hash(class_hash, compiled_class_hash) + } + + fn set_contract_class( + &mut self, + class_hash: &ClassHash, + contract_class: ContractClass, + ) -> StateResult<()> { + self.inner_mut().set_contract_class(class_hash, contract_class) + } + + fn set_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + value: StarkFelt, + ) { + self.inner_mut().set_storage_at(contract_address, key, value) + } + + fn to_state_diff(&self) -> CommitmentStateDiff { + self.inner_mut().to_state_diff() + } +} + +impl StateExt for CachedStateWrapper +where + Db: StateExtRef, +{ + fn set_sierra_class( + &mut self, + class_hash: ClassHash, + sierra_class: FlattenedSierraClass, + ) -> StateResult<()> { + self.sierra_class_mut().insert(class_hash, sierra_class); + Ok(()) + } +} + +impl StateReader for CachedStateWrapper +where + Db: StateExtRef, +{ + fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.inner_mut().get_class_hash_at(contract_address) + } + + fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { + self.inner_mut().get_compiled_class_hash(class_hash) + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + self.inner_mut().get_compiled_contract_class(class_hash) + } + + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.inner_mut().get_nonce_at(contract_address) + } + + fn get_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + self.inner_mut().get_storage_at(contract_address, key) + } +} + +impl StateExtRef for CachedStateWrapper +where + Db: StateExtRef, +{ + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { + if let Ok(class) = self.inner_mut().state.get_sierra_class(class_hash) { + return Ok(class); + } + + self.sierra_class() + .get(class_hash) + .cloned() + .ok_or(StateError::StateReadError("missing sierra class".to_string())) + } +} + +impl AsStateRefDb for CachedStateWrapper +where + Db: StateExtRef + Clone + Send + Sync + 'static, +{ + fn as_ref_db(&self) -> StateRefDb { + StateRefDb::new(self.clone()) + } +} + +/// Unit tests ported from `blockifier`. +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use blockifier::state::cached_state::CachedState; + use starknet_api::core::PatriciaKey; + use starknet_api::hash::StarkHash; + use starknet_api::{patricia_key, stark_felt}; + + use super::*; + use crate::backend::in_memory_db::MemDb; + use crate::db::Database; + + #[test] + fn get_inner_cached_db_if_exist() { + let mut db: Box = Box::new(MemDb::new()); + + let contract_address = ContractAddress(patricia_key!("123")); + let storage_key = StorageKey(patricia_key!("456")); + let storage_value = stark_felt!("0x1"); + + db.set_storage_at(contract_address, storage_key, storage_value); + + let has_value = db.maybe_as_cached_db().and_then(|db| { + db.storage + .get(&contract_address) + .and_then(|record| record.storage.get(&storage_key)) + .copied() + }); + + assert_matches!(has_value, Some(value) if value == storage_value); + } + + #[test] + fn get_uninitialized_storage_value() { + let mut state = CachedState::new(CachedDb::new(MemDb::new())); + let contract_address = ContractAddress(patricia_key!("0x1")); + let key = StorageKey(patricia_key!("0x10")); + assert_eq!(state.get_storage_at(contract_address, key).unwrap(), StarkFelt::default()); + } + + #[test] + fn get_and_set_storage_value() { + let contract_address0 = ContractAddress(patricia_key!("0x100")); + let contract_address1 = ContractAddress(patricia_key!("0x200")); + let key0 = StorageKey(patricia_key!("0x10")); + let key1 = StorageKey(patricia_key!("0x20")); + let storage_val0 = stark_felt!("0x1"); + let storage_val1 = stark_felt!("0x5"); + + let mut state = CachedState::new(CachedDb { + contracts: HashMap::from([ + (contract_address0, ClassHash(0_u32.into())), + (contract_address1, ClassHash(0_u32.into())), + ]), + storage: HashMap::from([ + ( + contract_address0, + StorageRecord { + nonce: Nonce(0_u32.into()), + storage: HashMap::from([(key0, storage_val0)]), + }, + ), + ( + contract_address1, + StorageRecord { + nonce: Nonce(0_u32.into()), + storage: HashMap::from([(key1, storage_val1)]), + }, + ), + ]), + classes: HashMap::new(), + sierra_classes: HashMap::new(), + db: MemDb::new(), + }); + + assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), storage_val0); + assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), storage_val1); + + let modified_storage_value0 = stark_felt!("0xA"); + state.set_storage_at(contract_address0, key0, modified_storage_value0); + assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), modified_storage_value0); + assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), storage_val1); + + let modified_storage_value1 = stark_felt!("0x7"); + state.set_storage_at(contract_address1, key1, modified_storage_value1); + assert_eq!(state.get_storage_at(contract_address0, key0).unwrap(), modified_storage_value0); + assert_eq!(state.get_storage_at(contract_address1, key1).unwrap(), modified_storage_value1); + } + + #[test] + fn get_uninitialized_value() { + let mut state = CachedState::new(CachedDb::new(MemDb::new())); + let contract_address = ContractAddress(patricia_key!("0x1")); + assert_eq!(state.get_nonce_at(contract_address).unwrap(), Nonce::default()); + } + + #[test] + fn get_uninitialized_class_hash_value() { + let mut state = CachedState::new(CachedDb::new(MemDb::new())); + let valid_contract_address = ContractAddress(patricia_key!("0x1")); + assert_eq!(state.get_class_hash_at(valid_contract_address).unwrap(), ClassHash::default()); + } + + #[test] + fn cannot_set_class_hash_to_uninitialized_contract() { + let mut state = CachedState::new(CachedDb::new(MemDb::new())); + let uninitialized_contract_address = ContractAddress::default(); + let class_hash = ClassHash(stark_felt!("0x100")); + assert_matches!( + state.set_class_hash_at(uninitialized_contract_address, class_hash).unwrap_err(), + StateError::OutOfRangeContractAddress + ); + } + + #[test] + fn get_and_increment_nonce() { + let contract_address1 = ContractAddress(patricia_key!("0x100")); + let contract_address2 = ContractAddress(patricia_key!("0x200")); + let initial_nonce = Nonce(stark_felt!("0x1")); + + let mut state = CachedState::new(CachedDb { + contracts: HashMap::from([ + (contract_address1, ClassHash(0_u32.into())), + (contract_address2, ClassHash(0_u32.into())), + ]), + storage: HashMap::from([ + ( + contract_address1, + StorageRecord { nonce: initial_nonce, storage: HashMap::new() }, + ), + ( + contract_address2, + StorageRecord { nonce: initial_nonce, storage: HashMap::new() }, + ), + ]), + classes: HashMap::new(), + sierra_classes: HashMap::new(), + db: MemDb::new(), + }); + + assert_eq!(state.get_nonce_at(contract_address1).unwrap(), initial_nonce); + assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); + + assert!(state.increment_nonce(contract_address1).is_ok()); + let nonce1_plus_one = Nonce(stark_felt!("0x2")); + assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_one); + assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); + + assert!(state.increment_nonce(contract_address1).is_ok()); + let nonce1_plus_two = Nonce(stark_felt!("0x3")); + assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_two); + assert_eq!(state.get_nonce_at(contract_address2).unwrap(), initial_nonce); + + assert!(state.increment_nonce(contract_address2).is_ok()); + let nonce2_plus_one = Nonce(stark_felt!("0x2")); + assert_eq!(state.get_nonce_at(contract_address1).unwrap(), nonce1_plus_two); + assert_eq!(state.get_nonce_at(contract_address2).unwrap(), nonce2_plus_one); + } +} diff --git a/crates/katana/core/src/db/mod.rs b/crates/katana/core/src/db/mod.rs index baefbc0ea1..de0334f7cf 100644 --- a/crates/katana/core/src/db/mod.rs +++ b/crates/katana/core/src/db/mod.rs @@ -1,20 +1,47 @@ -pub mod serde; +use std::fmt; +use std::sync::Arc; use anyhow::Result; -use blockifier::state::state_api::{State, StateReader}; +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::state_api::{State, StateReader, StateResult}; +use parking_lot::Mutex; +use starknet::core::types::FlattenedSierraClass; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; use starknet_api::hash::StarkHash; use starknet_api::patricia_key; use starknet_api::state::StorageKey; +use self::cached::MaybeAsCachedDb; use self::serde::state::SerializableState; -use crate::backend::state::StateExt; -pub trait Db: State + StateReader + StateExt { +pub mod cached; +pub mod serde; + +/// An extension of the [StateReader] trait, to allow fetching Sierra class from the state. +pub trait StateExtRef: StateReader + fmt::Debug { + /// Returns the Sierra class for the given class hash. + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult; +} + +/// An extension of the [State] trait, to allow storing Sierra classes. +pub trait StateExt: State + StateExtRef { + /// Set the Sierra class for the given class hash. + fn set_sierra_class( + &mut self, + class_hash: ClassHash, + sierra_class: FlattenedSierraClass, + ) -> StateResult<()>; +} + +/// A trait which represents a state database. +pub trait Database: StateExt + AsStateRefDb + Send + Sync + MaybeAsCachedDb { + /// Set the exact nonce value for the given contract address. fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce); + /// Returns the serialized version of the state. fn dump_state(&self) -> Result; + /// Load the serialized state into the current state. fn load_state(&mut self, state: SerializableState) -> Result<()> { for (addr, record) in state.storage { let address = ContractAddress(patricia_key!(addr)); @@ -23,10 +50,16 @@ pub trait Db: State + StateReader + StateExt { self.set_storage_at(address, StorageKey(patricia_key!(*key)), (*value).into()); }); - self.set_class_hash_at(address, ClassHash(record.class_hash.into()))?; self.set_nonce(address, Nonce(record.nonce.into())); } + for (address, class_hash) in state.contracts { + self.set_class_hash_at( + ContractAddress(patricia_key!(address)), + ClassHash(class_hash.into()), + )?; + } + for (hash, record) in state.classes { let hash = ClassHash(hash.into()); let compiled_hash = CompiledClassHash(record.compiled_hash.into()); @@ -43,3 +76,61 @@ pub trait Db: State + StateReader + StateExt { Ok(()) } } + +pub trait AsStateRefDb { + /// Returns the current state as a read only state + fn as_ref_db(&self) -> StateRefDb; +} + +/// A type which represents a state at a cetain point. This state type is only meant to be read +/// from. +/// +/// It implements [Clone] so that it can be cloned into a +/// [CachedState](blockifier::state::cached_state::CachedState) for executing transactions +/// based on this state, as [CachedState](blockifier::state::cached_state::CachedState) requires the +/// ownership of the inner [StateReader] that it wraps. +/// +/// The inner type is wrapped inside a [Mutex] to allow interior mutability due to the fact +/// that the [StateReader] trait requires mutable access to the type that implements it. +#[derive(Debug, Clone)] +pub struct StateRefDb(Arc>); + +impl StateRefDb { + pub fn new(state: T) -> Self + where + T: StateExtRef + Send + Sync + 'static, + { + Self(Arc::new(Mutex::new(state))) + } +} + +impl StateReader for StateRefDb { + fn get_storage_at(&mut self, addr: ContractAddress, key: StorageKey) -> StateResult { + self.0.lock().get_storage_at(addr, key) + } + + fn get_class_hash_at(&mut self, addr: ContractAddress) -> StateResult { + self.0.lock().get_class_hash_at(addr) + } + + fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { + self.0.lock().get_compiled_class_hash(class_hash) + } + + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.0.lock().get_nonce_at(contract_address) + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + self.0.lock().get_compiled_contract_class(class_hash) + } +} + +impl StateExtRef for StateRefDb { + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { + self.0.lock().get_sierra_class(class_hash) + } +} diff --git a/crates/katana/core/src/db/serde/state.rs b/crates/katana/core/src/db/serde/state.rs index edf0c84259..9fa0a68795 100644 --- a/crates/katana/core/src/db/serde/state.rs +++ b/crates/katana/core/src/db/serde/state.rs @@ -11,6 +11,8 @@ use crate::db::serde::contract::SerializableContractClass; #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct SerializableState { + /// Contract address to its class hash + pub contracts: BTreeMap, /// Address to storage record. pub storage: BTreeMap, /// Class hash to class record. @@ -30,7 +32,6 @@ pub struct SerializableClassRecord { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableStorageRecord { pub nonce: FieldElement, - pub class_hash: FieldElement, pub storage: BTreeMap, } diff --git a/crates/katana/core/src/env.rs b/crates/katana/core/src/env.rs index dfb86428ae..2af8cc563f 100644 --- a/crates/katana/core/src/env.rs +++ b/crates/katana/core/src/env.rs @@ -14,7 +14,7 @@ use starknet_api::patricia_key; use crate::constants::{DEFAULT_GAS_PRICE, FEE_TOKEN_ADDRESS, SEQUENCER_ADDRESS}; /// Represents the chain environment. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Env { /// The block environment of the current block. This is the context that /// the transactions will be executed on. diff --git a/crates/katana/core/src/execution.rs b/crates/katana/core/src/execution.rs new file mode 100644 index 0000000000..fddff2d714 --- /dev/null +++ b/crates/katana/core/src/execution.rs @@ -0,0 +1,393 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use blockifier::block_context::BlockContext; +use blockifier::execution::contract_class::ContractClass; +use blockifier::execution::entry_point::CallInfo; +use blockifier::state::cached_state::CommitmentStateDiff; +use blockifier::state::state_api::{State, StateReader}; +use blockifier::transaction::errors::TransactionExecutionError; +use blockifier::transaction::objects::{ResourcesMapping, TransactionExecutionInfo}; +use blockifier::transaction::transaction_execution::Transaction as ExecutionTransaction; +use blockifier::transaction::transactions::ExecutableTransaction; +use convert_case::{Case, Casing}; +use parking_lot::RwLock; +use starknet::core::types::{Event, ExecutionResult, FieldElement, FlattenedSierraClass, MsgToL1}; +use starknet_api::core::ClassHash; +use tracing::{trace, warn}; + +use crate::backend::storage::transaction::{ + DeclareTransaction, RejectedTransaction, Transaction, TransactionOutput, +}; +use crate::db::cached::CachedStateWrapper; +use crate::db::{Database, StateExt, StateRefDb}; + +/// The outcome that after executing a list of transactions. +pub struct ExecutionOutcome { + // states + pub state_diff: CommitmentStateDiff, + pub declared_classes: HashMap, + pub declared_sierra_classes: HashMap, + // transactions + pub transactions: Vec, +} + +impl ExecutionOutcome { + /// Apply the execution outcome to the given database. + pub fn apply_to(&self, db: &mut dyn Database) { + let ExecutionOutcome { state_diff, declared_classes, declared_sierra_classes, .. } = self; + + // update contract storages + state_diff.storage_updates.iter().for_each(|(contract_address, storages)| { + storages.iter().for_each(|(key, value)| { + db.set_storage_at(*contract_address, *key, *value); + }) + }); + + // update declared contracts + // apply newly declared classses + for (class_hash, compiled_class_hash) in &state_diff.class_hash_to_compiled_class_hash { + let contract_class = + declared_classes.get(class_hash).expect("contract class should exist").clone(); + + let is_sierra = matches!(contract_class, ContractClass::V1(_)); + + db.set_contract_class(class_hash, contract_class).unwrap(); + db.set_compiled_class_hash(*class_hash, *compiled_class_hash).unwrap(); + + if is_sierra { + if let Some(class) = declared_sierra_classes.get(class_hash).cloned() { + db.set_sierra_class(*class_hash, class).unwrap(); + } else { + panic!("sierra class definition is missing") + } + } + } + + // update deployed contracts + state_diff.address_to_class_hash.iter().for_each(|(contract_address, class_hash)| { + db.set_class_hash_at(*contract_address, *class_hash).unwrap() + }); + + // update accounts nonce + state_diff.address_to_nonce.iter().for_each(|(contract_address, nonce)| { + db.set_nonce(*contract_address, *nonce); + }); + } +} + +impl Default for ExecutionOutcome { + fn default() -> Self { + let state_diff = CommitmentStateDiff { + storage_updates: Default::default(), + address_to_nonce: Default::default(), + address_to_class_hash: Default::default(), + class_hash_to_compiled_class_hash: Default::default(), + }; + + Self { + state_diff, + transactions: Default::default(), + declared_classes: Default::default(), + declared_sierra_classes: Default::default(), + } + } +} + +pub struct TransactionExecutor<'a> { + charge_fee: bool, + block_context: &'a BlockContext, + state: &'a mut CachedStateWrapper, + + // logs flags + error_log: bool, + events_log: bool, + resources_log: bool, +} + +impl<'a> TransactionExecutor<'a> { + pub fn new( + state: &'a mut CachedStateWrapper, + block_context: &'a BlockContext, + charge_fee: bool, + ) -> Self { + Self { + state, + charge_fee, + block_context, + error_log: false, + events_log: false, + resources_log: false, + } + } + + pub fn with_events_log(self) -> Self { + Self { events_log: true, ..self } + } + + pub fn with_error_log(self) -> Self { + Self { error_log: true, ..self } + } + + pub fn with_resources_log(self) -> Self { + Self { resources_log: true, ..self } + } +} + +impl<'a> TransactionExecutor<'a> { + pub fn execute_many( + &mut self, + transactions: Vec, + ) -> Vec> { + transactions.into_iter().map(|tx| self.execute(tx)).collect() + } + + pub fn execute( + &mut self, + transaction: Transaction, + ) -> Result { + let sierra = if let Transaction::Declare(DeclareTransaction { + sierra_class: Some(sierra_class), + inner, + .. + }) = &transaction + { + Some((inner.class_hash(), sierra_class.clone())) + } else { + None + }; + + let res = match transaction.into() { + ExecutionTransaction::AccountTransaction(tx) => { + tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) + } + ExecutionTransaction::L1HandlerTransaction(tx) => { + tx.execute(&mut self.state.inner_mut(), self.block_context, self.charge_fee) + } + }; + + match res { + Ok(exec_info) => { + if let Some((class_hash, sierra_class)) = sierra { + self.state + .set_sierra_class(class_hash, sierra_class) + .expect("failed to set sierra class"); + } + + if self.error_log { + if let Some(err) = &exec_info.revert_error { + let formatted_err = format!("{:?}", err).replace("\\n", "\n"); + warn!(target: "executor", "Transaction execution error: {formatted_err}"); + } + } + + if self.resources_log { + trace!( + target: "executor", + "Transaction resource usage: {}", + pretty_print_resources(&exec_info.actual_resources) + ); + } + + if self.events_log { + trace_events(&events_from_exec_info(&exec_info)); + } + + Ok(exec_info) + } + + Err(err) => { + if self.error_log { + warn!(target: "executor", "Transaction validation error: {err:?}"); + } + + Err(err) + } + } + } +} + +/// An enum which represents a transaction that has been executed and may or may not be valid. +#[derive(Clone)] +pub enum MaybeInvalidExecutedTransaction { + Valid(Arc), + Invalid(Arc), +} + +pub struct PendingState { + pub state: RwLock>, + /// The transactions that have been executed. + pub executed_transactions: RwLock>, +} + +#[derive(Debug)] +pub struct ExecutedTransaction { + pub inner: Transaction, + pub output: TransactionOutput, + pub execution_info: TransactionExecutionInfo, +} + +impl ExecutedTransaction { + pub fn new(transaction: Transaction, execution_info: TransactionExecutionInfo) -> Self { + let actual_fee = execution_info.actual_fee.0; + let events = events_from_exec_info(&execution_info); + let messages_sent = l2_to_l1_messages_from_exec_info(&execution_info); + + Self { + execution_info, + inner: transaction, + output: TransactionOutput { actual_fee, events, messages_sent }, + } + } + + pub fn execution_result(&self) -> ExecutionResult { + if let Some(ref revert_err) = self.execution_info.revert_error { + ExecutionResult::Reverted { reason: revert_err.clone() } + } else { + ExecutionResult::Succeeded + } + } +} + +pub fn events_from_exec_info(execution_info: &TransactionExecutionInfo) -> Vec { + let mut events: Vec = vec![]; + + fn get_events_recursively(call_info: &CallInfo) -> Vec { + let mut events: Vec = vec![]; + + events.extend(call_info.execution.events.iter().map(|e| Event { + from_address: (*call_info.call.storage_address.0.key()).into(), + data: e.event.data.0.iter().map(|d| (*d).into()).collect(), + keys: e.event.keys.iter().map(|k| k.0.into()).collect(), + })); + + call_info.inner_calls.iter().for_each(|call| { + events.extend(get_events_recursively(call)); + }); + + events + } + + if let Some(ref call) = execution_info.validate_call_info { + events.extend(get_events_recursively(call)); + } + + if let Some(ref call) = execution_info.execute_call_info { + events.extend(get_events_recursively(call)); + } + + if let Some(ref call) = execution_info.fee_transfer_call_info { + events.extend(get_events_recursively(call)); + } + + events +} + +pub fn l2_to_l1_messages_from_exec_info(execution_info: &TransactionExecutionInfo) -> Vec { + let mut messages = vec![]; + + fn get_messages_recursively(info: &CallInfo) -> Vec { + let mut messages = vec![]; + + messages.extend(info.execution.l2_to_l1_messages.iter().map(|m| MsgToL1 { + to_address: + FieldElement::from_byte_slice_be(m.message.to_address.0.as_bytes()).unwrap(), + from_address: (*info.call.caller_address.0.key()).into(), + payload: m.message.payload.0.iter().map(|p| (*p).into()).collect(), + })); + + info.inner_calls.iter().for_each(|call| { + messages.extend(get_messages_recursively(call)); + }); + + messages + } + + if let Some(ref info) = execution_info.validate_call_info { + messages.extend(get_messages_recursively(info)); + } + + if let Some(ref info) = execution_info.execute_call_info { + messages.extend(get_messages_recursively(info)); + } + + if let Some(ref info) = execution_info.fee_transfer_call_info { + messages.extend(get_messages_recursively(info)); + } + + messages +} + +fn pretty_print_resources(resources: &ResourcesMapping) -> String { + let mut mapped_strings: Vec<_> = resources + .0 + .iter() + .filter_map(|(k, v)| match k.as_str() { + "l1_gas_usage" => Some(format!("L1 Gas: {}", v)), + "range_check_builtin" => Some(format!("Range Checks: {}", v)), + "ecdsa_builtin" => Some(format!("ECDSA: {}", v)), + "n_steps" => None, + "pedersen_builtin" => Some(format!("Pedersen: {}", v)), + "bitwise_builtin" => Some(format!("Bitwise: {}", v)), + "keccak_builtin" => Some(format!("Keccak: {}", v)), + _ => Some(format!("{}: {}", k.to_case(Case::Title), v)), + }) + .collect::>(); + + // Sort the strings alphabetically + mapped_strings.sort(); + + // Prepend "Steps" if it exists, so it is always first + if let Some(steps) = resources.0.get("n_steps") { + mapped_strings.insert(0, format!("Steps: {}", steps)); + } + + mapped_strings.join(" | ") +} + +fn trace_events(events: &[Event]) { + for e in events { + let formatted_keys = + e.keys.iter().map(|k| format!("{k:#x}")).collect::>().join(", "); + + trace!(target: "executor", "Event emitted keys=[{}]", formatted_keys); + } +} + +pub fn create_execution_outcome( + state: &mut CachedStateWrapper, + transactions: Vec<(Transaction, Result)>, +) -> ExecutionOutcome { + let transactions = transactions + .into_iter() + .map(|(tx, res)| match res { + Ok(exec_info) => MaybeInvalidExecutedTransaction::Valid(Arc::new( + ExecutedTransaction::new(tx, exec_info), + )), + + Err(err) => MaybeInvalidExecutedTransaction::Invalid(Arc::new(RejectedTransaction { + inner: tx, + execution_error: err.to_string(), + })), + }) + .collect::>(); + + let state_diff = state.to_state_diff(); + let declared_classes = state_diff + .class_hash_to_compiled_class_hash + .iter() + .map(|(class_hash, _)| { + let contract_class = state + .get_compiled_contract_class(class_hash) + .expect("contract class must exist in state if declared"); + (*class_hash, contract_class) + }) + .collect::>(); + + ExecutionOutcome { + state_diff, + transactions, + declared_classes, + declared_sierra_classes: state.sierra_class().clone(), + } +} diff --git a/crates/katana/core/src/fork/backend.rs b/crates/katana/core/src/fork/backend.rs new file mode 100644 index 0000000000..ed77f2343c --- /dev/null +++ b/crates/katana/core/src/fork/backend.rs @@ -0,0 +1,475 @@ +use std::collections::VecDeque; +use std::pin::Pin; +use std::sync::mpsc::{channel as oneshot, Sender as OneshotSender}; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::thread; + +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::{StateReader, StateResult}; +use futures::channel::mpsc::{channel, Receiver, Sender, TrySendError}; +use futures::stream::Stream; +use futures::{Future, FutureExt}; +use parking_lot::RwLock; +use starknet::core::types::{BlockId, FieldElement, FlattenedSierraClass, StarknetError}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{ + JsonRpcClient, MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, +}; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; +use tracing::trace; + +use crate::db::cached::CachedDb; +use crate::db::StateExtRef; +use crate::utils::contract::{ + compiled_class_hash_from_flattened_sierra_class, legacy_rpc_to_inner_class, rpc_to_inner_class, +}; + +type GetNonceResult = Result; +type GetStorageResult = Result; +type GetClassHashAtResult = Result; +type GetClassAtResult = Result; + +#[derive(Debug, thiserror::Error)] +pub enum ForkedBackendError { + #[error(transparent)] + Send(TrySendError), + #[error("Compute class hash error: {0}")] + ComputeClassHashError(String), + #[error(transparent)] + Provider(ProviderError< as Provider>::Error>), +} + +pub enum BackendRequest { + GetClassAt(ClassHash, OneshotSender), + GetNonce(ContractAddress, OneshotSender), + GetClassHashAt(ContractAddress, OneshotSender), + GetStorage(ContractAddress, StorageKey, OneshotSender), +} + +type BackendRequestFuture = Pin + Send>>; + +/// A thread-safe handler for the shared forked backend. This handler is responsible for receiving +/// requests from all instances of the [ForkedBackend], process them, and returns the results back +/// to the request sender. +pub struct BackendHandler { + provider: Arc>, + /// Requests that are currently being poll. + pending_requests: Vec, + /// Requests that are queued to be polled. + queued_requests: VecDeque, + /// A channel for receiving requests from the [ForkedBackend]'s. + incoming: Receiver, + /// Pinned block id for all requests. + block: BlockId, +} + +impl BackendHandler { + /// This function is responsible for transforming the incoming request + /// into a future that will be polled until completion by the `BackendHandler`. + /// + /// Each request is accompanied by the sender-half of a oneshot channel that will be used + /// to send the result back to the [ForkedBackend] which sent the requests. + fn handle_requests(&mut self, request: BackendRequest) { + let block = self.block; + let provider = self.provider.clone(); + + match request { + BackendRequest::GetNonce(contract_address, sender) => { + let fut = Box::pin(async move { + let contract_address: FieldElement = (*contract_address.0.key()).into(); + + let res = provider + .get_nonce(block, contract_address) + .await + .map(|n| Nonce(n.into())) + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send nonce result") + }); + + self.pending_requests.push(fut); + } + + BackendRequest::GetStorage(contract_address, key, sender) => { + let fut = Box::pin(async move { + let contract_address: FieldElement = (*contract_address.0.key()).into(); + let key: FieldElement = (*key.0.key()).into(); + + let res = provider + .get_storage_at(contract_address, key, block) + .await + .map(|f| f.into()) + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send storage result") + }); + + self.pending_requests.push(fut); + } + + BackendRequest::GetClassHashAt(contract_address, sender) => { + let fut = Box::pin(async move { + let contract_address: FieldElement = (*contract_address.0.key()).into(); + + let res = provider + .get_class_hash_at(block, contract_address) + .await + .map(|f| ClassHash(f.into())) + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send class hash result") + }); + + self.pending_requests.push(fut); + } + + BackendRequest::GetClassAt(class_hash, sender) => { + let fut = Box::pin(async move { + let class_hash: FieldElement = class_hash.0.into(); + + let res = provider + .get_class(block, class_hash) + .await + .map_err(ForkedBackendError::Provider); + + sender.send(res).expect("failed to send class result") + }); + + self.pending_requests.push(fut); + } + } + } +} + +impl Future for BackendHandler { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pin = self.get_mut(); + loop { + // convert all queued requests into futures to be polled + while let Some(req) = pin.queued_requests.pop_front() { + pin.handle_requests(req); + } + + loop { + match Pin::new(&mut pin.incoming).poll_next(cx) { + Poll::Ready(Some(req)) => { + pin.queued_requests.push_back(req); + } + // Resolve if stream is exhausted. + Poll::Ready(None) => { + return Poll::Ready(()); + } + Poll::Pending => { + break; + } + } + } + + // poll all pending requests + for n in (0..pin.pending_requests.len()).rev() { + let mut fut = pin.pending_requests.swap_remove(n); + // poll the future and if the future is still pending, push it back to the + // pending requests so that it will be polled again + if fut.poll_unpin(cx).is_pending() { + pin.pending_requests.push(fut); + } + } + + // if no queued requests, then yield + if pin.queued_requests.is_empty() { + return Poll::Pending; + } + } + } +} + +#[derive(Debug, Clone)] +pub struct SharedBackend { + cache: Arc>>, +} + +impl SharedBackend { + pub fn new_with_backend_thread( + provider: Arc>, + block: BlockId, + ) -> Self { + let backend = ForkedBackend::spawn_thread(provider, block); + Self { cache: Arc::new(RwLock::new(CachedDb::new(backend))) } + } +} + +impl StateReader for SharedBackend { + fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.cache.write().get_class_hash_at(contract_address) + } + + fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { + self.cache.write().get_compiled_class_hash(class_hash) + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + self.cache.write().get_compiled_contract_class(class_hash) + } + + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.cache.write().get_nonce_at(contract_address) + } + + fn get_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + self.cache.write().get_storage_at(contract_address, key) + } +} + +impl StateExtRef for SharedBackend { + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { + self.cache.write().get_sierra_class(class_hash) + } +} + +/// An interface for interacting with a forked backend handler. This interface will be cloned into +/// multiple instances of the [ForkedBackend] and will be used to send requests to the handler. +#[derive(Debug, Clone)] +pub struct ForkedBackend { + handler: Sender, +} + +impl ForkedBackend { + pub fn spawn_thread(provider: Arc>, block: BlockId) -> Self { + let (backend, handler) = Self::new(provider, block); + + thread::Builder::new() + .spawn(move || { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to create fork backend thread tokio runtime"); + + rt.block_on(handler); + }) + .expect("failed to spawn fork backend thread"); + + trace!(target: "forked_backend", "fork backend thread spawned"); + + backend + } + + pub fn new( + provider: Arc>, + block: BlockId, + ) -> (Self, BackendHandler) { + let (sender, rx) = channel(1); + let handler = BackendHandler { + incoming: rx, + provider, + block, + queued_requests: VecDeque::new(), + pending_requests: Vec::new(), + }; + (Self { handler: sender }, handler) + } + + pub fn do_get_nonce( + &mut self, + contract_address: ContractAddress, + ) -> Result { + trace!(target: "forked_backend", "request nonce for contract address {}", contract_address.0.key()); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.handler + .try_send(BackendRequest::GetNonce(contract_address, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive nonce result") + }) + } + + pub fn do_get_storage( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> Result { + trace!(target: "forked_backend", "request storage for address {} at key {}", contract_address.0.key(), key.0.key()); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.handler + .try_send(BackendRequest::GetStorage(contract_address, key, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive storage result") + }) + } + + pub fn do_get_class_hash_at( + &mut self, + contract_address: ContractAddress, + ) -> Result { + trace!(target: "forked_backend", "request class hash at address {}", contract_address.0.key()); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.handler + .try_send(BackendRequest::GetClassHashAt(contract_address, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive class hash result") + }) + } + + pub fn do_get_class_at( + &mut self, + class_hash: ClassHash, + ) -> Result { + trace!(target: "forked_backend", "request class at hash {}", class_hash.0); + tokio::task::block_in_place(|| { + let (sender, rx) = oneshot(); + self.handler + .try_send(BackendRequest::GetClassAt(class_hash, sender)) + .map_err(ForkedBackendError::Send)?; + rx.recv().expect("failed to receive class result") + }) + } + + pub fn do_get_compiled_class_hash( + &mut self, + class_hash: ClassHash, + ) -> Result { + trace!(target: "forked_backend", "request compiled class hash at class {}", class_hash.0); + let class = self.do_get_class_at(class_hash)?; + // if its a legacy class, then we just return back the class hash + // else if sierra class, then we have to compile it and compute the compiled class hash. + match class { + starknet::core::types::ContractClass::Legacy(_) => Ok(CompiledClassHash(class_hash.0)), + + starknet::core::types::ContractClass::Sierra(sierra_class) => { + tokio::task::block_in_place(|| { + compiled_class_hash_from_flattened_sierra_class(&sierra_class) + }) + .map(|f| CompiledClassHash(f.into())) + .map_err(|e| ForkedBackendError::ComputeClassHashError(e.to_string())) + } + } + } +} + +impl StateReader for ForkedBackend { + fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { + match self.do_get_compiled_class_hash(class_hash) { + Ok(compiled_class_hash) => Ok(compiled_class_hash), + + Err(ForkedBackendError::Provider(ProviderError::StarknetError( + StarknetErrorWithMessage { + code: MaybeUnknownErrorCode::Known(StarknetError::ClassHashNotFound), + .. + }, + ))) => Err(StateError::UndeclaredClassHash(class_hash)), + Err(e) => Err(StateError::StateReadError(e.to_string())), + } + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + match self.do_get_class_at(*class_hash) { + Ok(class) => match class { + starknet::core::types::ContractClass::Legacy(legacy_class) => { + legacy_rpc_to_inner_class(&legacy_class) + .map(|(_, class)| class) + .map_err(|e| StateError::StateReadError(e.to_string())) + } + + starknet::core::types::ContractClass::Sierra(sierra_class) => { + rpc_to_inner_class(&sierra_class) + .map(|(_, class)| class) + .map_err(|e| StateError::StateReadError(e.to_string())) + } + }, + + Err(ForkedBackendError::Provider(ProviderError::StarknetError( + StarknetErrorWithMessage { + code: MaybeUnknownErrorCode::Known(StarknetError::ClassHashNotFound), + .. + }, + ))) => Err(StateError::UndeclaredClassHash(*class_hash)), + + Err(e) => Err(StateError::StateReadError(e.to_string())), + } + } + + fn get_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + match self.do_get_storage(contract_address, key) { + Ok(value) => Ok(value), + + Err(ForkedBackendError::Provider(ProviderError::StarknetError( + StarknetErrorWithMessage { + code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), + .. + }, + ))) => Ok(StarkFelt::default()), + + Err(e) => Err(StateError::StateReadError(e.to_string())), + } + } + + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + match self.do_get_nonce(contract_address) { + Ok(nonce) => Ok(nonce), + + Err(ForkedBackendError::Provider(ProviderError::StarknetError( + StarknetErrorWithMessage { + code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), + .. + }, + ))) => Ok(Nonce::default()), + + Err(e) => Err(StateError::StateReadError(e.to_string())), + } + } + + fn get_class_hash_at(&mut self, contract_address: ContractAddress) -> StateResult { + match self.do_get_class_hash_at(contract_address) { + Ok(class_hash) => Ok(class_hash), + + Err(ForkedBackendError::Provider(ProviderError::StarknetError( + StarknetErrorWithMessage { + code: MaybeUnknownErrorCode::Known(StarknetError::ContractNotFound), + .. + }, + ))) => Ok(ClassHash::default()), + + Err(e) => Err(StateError::StateReadError(e.to_string())), + } + } +} + +impl StateExtRef for ForkedBackend { + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { + match self.do_get_class_at(*class_hash) { + Ok(starknet::core::types::ContractClass::Sierra(sierra_class)) => Ok(sierra_class), + + Ok(_) => Err(StateError::StateReadError("Class hash is not a Sierra class".into())), + + Err(ForkedBackendError::Provider(ProviderError::StarknetError( + StarknetErrorWithMessage { + code: MaybeUnknownErrorCode::Known(StarknetError::ClassHashNotFound), + .. + }, + ))) => Err(StateError::UndeclaredClassHash(*class_hash)), + + Err(e) => Err(StateError::StateReadError(e.to_string())), + } + } +} diff --git a/crates/katana/core/src/fork/db.rs b/crates/katana/core/src/fork/db.rs new file mode 100644 index 0000000000..f7d55bb77a --- /dev/null +++ b/crates/katana/core/src/fork/db.rs @@ -0,0 +1,287 @@ +use std::collections::BTreeMap; +use std::sync::Arc; + +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::cached_state::CommitmentStateDiff; +use blockifier::state::state_api::{State, StateReader, StateResult}; +use starknet::core::types::{BlockId, FlattenedSierraClass}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; + +use super::backend::SharedBackend; +use crate::db::cached::{AsCachedDb, CachedDb, MaybeAsCachedDb}; +use crate::db::serde::state::{ + SerializableClassRecord, SerializableState, SerializableStorageRecord, +}; +use crate::db::{AsStateRefDb, Database, StateExt, StateExtRef, StateRefDb}; + +/// A state database implementation that forks from a network. +/// +/// It will try to find the requested data in the cache, and if it's not there, it will fetch it +/// from the forked network. The fetched data will be stored in the cache so that the next time the +/// same data is requested, it will be fetched from the cache instead of fetching it from the forked +/// network again. +/// +/// The forked database provider should be locked to a particular block. +#[derive(Debug, Clone)] +pub struct ForkedDb { + /// Shared cache of the forked database. This will be shared across all instances of the + /// `ForkedDb` when it is cloned into a [StateRefDb] using the [AsStateRefDb] trait. + /// + /// So if one instance fetches data from the forked network, the + /// other instances will be able to use the cached data instead of fetching it again. + db: CachedDb, +} + +impl ForkedDb { + /// Construct a new `ForkedDb` from a `Provider` of the network to fork from at a particular + /// `block`. + pub fn new(provider: Arc>, block: BlockId) -> Self { + Self { db: CachedDb::new(SharedBackend::new_with_backend_thread(provider, block)) } + } + + #[cfg(test)] + pub fn new_from_backend(db: CachedDb) -> Self { + Self { db } + } +} + +impl State for ForkedDb { + fn set_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + value: StarkFelt, + ) { + self.db.set_storage_at(contract_address, key, value); + } + + fn set_class_hash_at( + &mut self, + contract_address: ContractAddress, + class_hash: ClassHash, + ) -> StateResult<()> { + self.db.set_class_hash_at(contract_address, class_hash) + } + + fn set_compiled_class_hash( + &mut self, + class_hash: ClassHash, + compiled_class_hash: CompiledClassHash, + ) -> StateResult<()> { + self.db.set_compiled_class_hash(class_hash, compiled_class_hash) + } + + fn to_state_diff(&self) -> CommitmentStateDiff { + self.db.to_state_diff() + } + + fn set_contract_class( + &mut self, + class_hash: &ClassHash, + contract_class: ContractClass, + ) -> StateResult<()> { + self.db.set_contract_class(class_hash, contract_class) + } + + fn increment_nonce(&mut self, contract_address: ContractAddress) -> StateResult<()> { + self.db.increment_nonce(contract_address) + } +} + +impl StateReader for ForkedDb { + fn get_nonce_at(&mut self, contract_address: ContractAddress) -> StateResult { + self.db.get_nonce_at(contract_address) + } + + fn get_storage_at( + &mut self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + self.db.get_storage_at(contract_address, key) + } + + fn get_class_hash_at( + &mut self, + contract_address: ContractAddress, + ) -> StateResult { + self.db.get_class_hash_at(contract_address) + } + + fn get_compiled_class_hash(&mut self, class_hash: ClassHash) -> StateResult { + self.db.get_compiled_class_hash(class_hash) + } + + fn get_compiled_contract_class( + &mut self, + class_hash: &ClassHash, + ) -> StateResult { + self.db.get_compiled_contract_class(class_hash) + } +} + +impl StateExtRef for ForkedDb { + fn get_sierra_class(&mut self, class_hash: &ClassHash) -> StateResult { + self.db.get_sierra_class(class_hash) + } +} + +impl StateExt for ForkedDb { + fn set_sierra_class( + &mut self, + class_hash: ClassHash, + sierra_class: FlattenedSierraClass, + ) -> StateResult<()> { + self.db.set_sierra_class(class_hash, sierra_class) + } +} + +impl AsStateRefDb for ForkedDb { + fn as_ref_db(&self) -> StateRefDb { + StateRefDb::new(self.clone()) + } +} + +impl MaybeAsCachedDb for ForkedDb { + fn maybe_as_cached_db(&self) -> Option { + Some(CachedDb { + db: (), + classes: self.db.classes.clone(), + storage: self.db.storage.clone(), + contracts: self.db.contracts.clone(), + sierra_classes: self.db.sierra_classes.clone(), + }) + } +} + +impl Database for ForkedDb { + fn set_nonce(&mut self, addr: ContractAddress, nonce: Nonce) { + self.db.storage.entry(addr).or_default().nonce = nonce; + } + + fn dump_state(&self) -> anyhow::Result { + let mut serializable = SerializableState::default(); + + self.db.storage.iter().for_each(|(addr, storage)| { + let mut record = SerializableStorageRecord { + storage: BTreeMap::new(), + nonce: storage.nonce.0.into(), + }; + + storage.storage.iter().for_each(|(key, value)| { + record.storage.insert((*key.0.key()).into(), (*value).into()); + }); + + serializable.storage.insert((*addr.0.key()).into(), record); + }); + + self.db.classes.iter().for_each(|(class_hash, class_record)| { + serializable.classes.insert( + class_hash.0.into(), + SerializableClassRecord { + class: class_record.class.clone().into(), + compiled_hash: class_record.compiled_hash.0.into(), + }, + ); + }); + + self.db.contracts.iter().for_each(|(address, class_hash)| { + serializable.contracts.insert((*address.0.key()).into(), class_hash.0.into()); + }); + + self.db.sierra_classes.iter().for_each(|(class_hash, class)| { + serializable.sierra_classes.insert(class_hash.0.into(), class.clone()); + }); + + Ok(serializable) + } +} + +#[cfg(test)] +mod tests { + use starknet::core::types::BlockTag; + use starknet::providers::jsonrpc::HttpTransport; + use starknet::providers::JsonRpcClient; + use starknet_api::core::PatriciaKey; + use starknet_api::hash::StarkHash; + use starknet_api::{patricia_key, stark_felt}; + use url::Url; + + use super::*; + use crate::constants::UDC_CONTRACT; + + const FORKED_ENDPOINT: &str = + "https://starknet-goerli.infura.io/v3/369ce5ac40614952af936e4d64e40474"; + + #[tokio::test] + async fn fetch_from_cache_if_exist() { + let address = ContractAddress(patricia_key!(0x1u32)); + let class_hash = ClassHash(stark_felt!(0x88u32)); + + let expected_nonce = Nonce(stark_felt!(44u32)); + let expected_storage_key = StorageKey(patricia_key!(0x2u32)); + let expected_storage_value = stark_felt!(55u32); + let expected_compiled_class_hash = CompiledClassHash(class_hash.0); + let expected_contract_class = (*UDC_CONTRACT).clone(); + + let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); + let mut cache = CachedDb::new(SharedBackend::new_with_backend_thread( + Arc::new(provider), + BlockId::Tag(BlockTag::Latest), + )); + + cache.storage.entry(address).or_default().nonce = expected_nonce; + cache.set_storage_at(address, expected_storage_key, expected_storage_value); + cache.set_contract_class(&class_hash, expected_contract_class.clone()).unwrap(); + cache.set_compiled_class_hash(class_hash, expected_compiled_class_hash).unwrap(); + + let mut db = ForkedDb::new_from_backend(cache); + + let nonce = db.get_nonce_at(address).unwrap(); + let storage_value = db.get_storage_at(address, expected_storage_key).unwrap(); + let contract_class = db.get_compiled_contract_class(&class_hash).unwrap(); + let compiled_class_hash = db.get_compiled_class_hash(class_hash).unwrap(); + + assert_eq!(nonce, expected_nonce); + assert_eq!(storage_value, expected_storage_value); + assert_eq!(contract_class, expected_contract_class); + assert_eq!(compiled_class_hash, expected_compiled_class_hash) + } + + #[tokio::test(flavor = "multi_thread")] + #[ignore] + async fn fetch_from_provider_if_not_in_cache() { + let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(FORKED_ENDPOINT).unwrap())); + let mut db = ForkedDb::new(Arc::new(provider), BlockId::Tag(BlockTag::Latest)); + + let address = ContractAddress(patricia_key!( + "0x02b92ec12cA1e308f320e99364d4dd8fcc9efDAc574F836C8908de937C289974" + )); + let storage_key = StorageKey(patricia_key!( + "0x3b459c3fadecdb1a501f2fdeec06fd735cb2d93ea59779177a0981660a85352" + )); + + let class_hash = db.get_class_hash_at(address).unwrap(); + let class = db.get_compiled_contract_class(&class_hash).unwrap(); + let storage_value = db.get_storage_at(address, storage_key).unwrap(); + + let expected_class_hash = ClassHash(stark_felt!( + "0x01a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003" + )); + + assert_eq!(class_hash, expected_class_hash); + + let class_hash_in_cache = *db.db.contracts.get(&address).unwrap(); + let class_in_cache = db.db.classes.get(&class_hash).unwrap().class.clone(); + let storage_value_in_cache = + *db.db.storage.get(&address).unwrap().storage.get(&storage_key).unwrap(); + + assert_eq!(class_in_cache, class, "class must be stored in cache"); + assert_eq!(class_hash_in_cache, expected_class_hash, "class hash must be stored in cache"); + assert_eq!(storage_value_in_cache, storage_value, "storage value must be stored in cache"); + } +} diff --git a/crates/katana/core/src/fork/mod.rs b/crates/katana/core/src/fork/mod.rs new file mode 100644 index 0000000000..b1a4440c59 --- /dev/null +++ b/crates/katana/core/src/fork/mod.rs @@ -0,0 +1,2 @@ +pub mod backend; +pub mod db; diff --git a/crates/katana/core/src/lib.rs b/crates/katana/core/src/lib.rs index 3c0a67e401..c80c58031f 100644 --- a/crates/katana/core/src/lib.rs +++ b/crates/katana/core/src/lib.rs @@ -3,7 +3,11 @@ pub mod backend; pub mod constants; pub mod db; pub mod env; +pub mod execution; +pub mod fork; +pub mod pool; pub mod sequencer; +pub mod service; pub mod utils; pub mod sequencer_error; diff --git a/crates/katana/core/src/pool.rs b/crates/katana/core/src/pool.rs new file mode 100644 index 0000000000..173091862d --- /dev/null +++ b/crates/katana/core/src/pool.rs @@ -0,0 +1,74 @@ +// Code adapted from Foundry's Anvil + +use futures::channel::mpsc::{channel, Receiver, Sender}; +use parking_lot::RwLock; +use starknet::core::types::FieldElement; +use tracing::{info, warn}; + +use crate::backend::storage::transaction::Transaction; + +#[derive(Debug, Default)] +pub struct TransactionPool { + transactions: RwLock>, + transaction_listeners: RwLock>>, +} + +impl TransactionPool { + pub fn new() -> Self { + Self::default() + } +} + +impl TransactionPool { + pub fn add_transaction(&self, transaction: Transaction) { + let hash = transaction.hash(); + self.transactions.write().push(transaction); + + info!(target: "txpool", "Transaction received | Hash: {hash:#x}"); + + // notify listeners of new tx added to the pool + self.notify_listener(hash) + } + + pub fn add_listener(&self) -> Receiver { + const TX_LISTENER_BUFFER_SIZE: usize = 2048; + let (tx, rx) = channel(TX_LISTENER_BUFFER_SIZE); + self.transaction_listeners.write().push(tx); + rx + } + + /// Get all the transaction from the pool and clear it. + pub fn get_transactions(&self) -> Vec { + let mut txs = self.transactions.write(); + let transactions = txs.clone(); + txs.clear(); + transactions + } + + /// notifies all listeners about the transaction + fn notify_listener(&self, hash: FieldElement) { + let mut listener = self.transaction_listeners.write(); + // this is basically a retain but with mut reference + for n in (0..listener.len()).rev() { + let mut listener_tx = listener.swap_remove(n); + let retain = match listener_tx.try_send(hash) { + Ok(()) => true, + Err(e) => { + if e.is_full() { + warn!( + target: "txpool", + "[{:?}] Failed to send tx notification because channel is full", + hash, + ); + true + } else { + false + } + } + }; + if retain { + listener.push(listener_tx) + } + } + } +} diff --git a/crates/katana/core/src/sequencer.rs b/crates/katana/core/src/sequencer.rs index cdd338c504..59e56f650f 100644 --- a/crates/katana/core/src/sequencer.rs +++ b/crates/katana/core/src/sequencer.rs @@ -4,11 +4,8 @@ use std::slice::Iter; use std::sync::Arc; use anyhow::Result; -use async_trait::async_trait; -use auto_impl::auto_impl; use blockifier::execution::contract_class::ContractClass; -use blockifier::state::state_api::StateReader; -use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::state::state_api::{State, StateReader}; use starknet::core::types::{ BlockId, BlockTag, EmittedEvent, Event, EventsPage, FeeEstimate, FieldElement, MaybePendingTransactionReceipt, StateUpdate, @@ -16,18 +13,21 @@ use starknet::core::types::{ use starknet_api::core::{ChainId, ClassHash, ContractAddress, Nonce}; use starknet_api::hash::StarkFelt; use starknet_api::state::StorageKey; -use tokio::time; use crate::backend::config::StarknetConfig; use crate::backend::contract::StarknetContract; -use crate::backend::state::{MemDb, StateExt}; -use crate::backend::storage::block::ExecutedBlock; +use crate::backend::storage::block::{ExecutedBlock, PartialBlock, PartialHeader}; use crate::backend::storage::transaction::{ DeclareTransaction, DeployAccountTransaction, InvokeTransaction, KnownTransaction, - PendingTransaction, Transaction, TransactionStatus, + PendingTransaction, Transaction, }; use crate::backend::{Backend, ExternalFunctionCall}; +use crate::db::{AsStateRefDb, StateExtRef, StateRefDb}; +use crate::execution::{MaybeInvalidExecutedTransaction, PendingState}; +use crate::pool::TransactionPool; use crate::sequencer_error::SequencerError; +use crate::service::block_producer::{BlockProducer, BlockProducerMode}; +use crate::service::{NodeService, TransactionMiner}; use crate::utils::event::{ContinuationToken, ContinuationTokenError}; type SequencerResult = Result; @@ -35,139 +35,49 @@ type SequencerResult = Result; #[derive(Debug, Default)] pub struct SequencerConfig { pub block_time: Option, -} - -#[async_trait] -#[auto_impl(Arc)] -pub trait Sequencer { - fn backend(&self) -> &Backend; - - async fn state(&self, block_id: &BlockId) -> SequencerResult; - - async fn chain_id(&self) -> ChainId; - - async fn transaction_receipt( - &self, - hash: &FieldElement, - ) -> Option; - - async fn transaction_status(&self, hash: &FieldElement) -> Option; - - async fn nonce_at( - &self, - block_id: BlockId, - contract_address: ContractAddress, - ) -> SequencerResult; - - async fn block_number(&self) -> u64; - - async fn block(&self, block_id: BlockId) -> Option; - - async fn transaction(&self, hash: &FieldElement) -> Option; - - async fn class_hash_at( - &self, - block_id: BlockId, - contract_address: ContractAddress, - ) -> SequencerResult; - - async fn class( - &self, - block_id: BlockId, - class_hash: ClassHash, - ) -> SequencerResult; - - async fn block_hash_and_number(&self) -> (FieldElement, u64); - - async fn call( - &self, - block_id: BlockId, - function_call: ExternalFunctionCall, - ) -> SequencerResult>; - - async fn storage_at( - &self, - contract_address: ContractAddress, - storage_key: StorageKey, - block_id: BlockId, - ) -> SequencerResult; - - async fn add_deploy_account_transaction( - &self, - transaction: DeployAccountTransaction, - ) -> (FieldElement, FieldElement); - - async fn add_declare_transaction(&self, transaction: DeclareTransaction); - - async fn add_invoke_transaction(&self, transaction: InvokeTransaction); - - async fn estimate_fee( - &self, - account_transaction: AccountTransaction, - block_id: BlockId, - ) -> SequencerResult; - - async fn events( - &self, - from_block: BlockId, - to_block: BlockId, - address: Option, - keys: Option>>, - continuation_token: Option, - chunk_size: u64, - ) -> SequencerResult; - - async fn state_update(&self, block_id: BlockId) -> SequencerResult; + pub no_mining: bool, } pub struct KatanaSequencer { pub config: SequencerConfig, + pub pool: Arc, pub backend: Arc, + pub block_producer: BlockProducer, } impl KatanaSequencer { - pub fn new(config: SequencerConfig, starknet_config: StarknetConfig) -> Self { - Self { config, backend: Arc::new(Backend::new(starknet_config)) } - } - - pub async fn start(&self) { - // self.starknet.generate_genesis_block().await; - - if let Some(block_time) = self.config.block_time { - let starknet = self.backend.clone(); - tokio::spawn(async move { - loop { - starknet.open_pending_block().await; - time::sleep(time::Duration::from_secs(block_time)).await; - starknet.mine_block().await; - } - }); + pub async fn new(config: SequencerConfig, starknet_config: StarknetConfig) -> Self { + let backend = Arc::new(Backend::new(starknet_config).await); + + let pool = Arc::new(TransactionPool::new()); + let miner = TransactionMiner::new(pool.add_listener()); + + let block_producer = if let Some(block_time) = config.block_time { + BlockProducer::interval( + Arc::clone(&backend), + backend.state.read().await.as_ref_db(), + block_time, + ) + } else if config.no_mining { + BlockProducer::on_demand(Arc::clone(&backend), backend.state.read().await.as_ref_db()) } else { - self.backend.open_pending_block().await; - } - } - - // pub async fn drip_and_deploy_account( - // &self, - // transaction: DeployAccountTransaction, - // balance: u64, - // ) -> SequencerResult<(TransactionHash, ContractAddress)> { let (transaction_hash, - // contract_address) = self.add_deploy_account_transaction(transaction).await; + BlockProducer::instant(Arc::clone(&backend)) + }; - // let deployed_account_balance_key = - // get_storage_var_address("ERC20_balances", &[*contract_address.0.key()]) - // .map_err(SequencerError::StarknetApi)?; + tokio::spawn(NodeService::new(Arc::clone(&pool), miner, block_producer.clone())); - // self.starknet.pending_cached_state.write().await.set_storage_at( - // self.starknet.block_context.read().fee_token_address, - // deployed_account_balance_key, - // stark_felt!(balance), - // ); + Self { pool, config, backend, block_producer } + } - // Ok((transaction_hash, contract_address)) - // } + /// Returns the pending state if the sequencer is running in _interval_ mode. Otherwise `None`. + pub fn pending_state(&self) -> Option> { + match &*self.block_producer.inner.read() { + BlockProducerMode::Instant(_) => None, + BlockProducerMode::Interval(producer) => Some(producer.state()), + } + } - pub(self) async fn verify_contract_exists(&self, contract_address: &ContractAddress) -> bool { + async fn verify_contract_exists(&self, contract_address: &ContractAddress) -> bool { self.backend .state .write() @@ -175,24 +85,29 @@ impl KatanaSequencer { .get_class_hash_at(*contract_address) .is_ok_and(|c| c != ClassHash::default()) } -} -#[async_trait] -impl Sequencer for KatanaSequencer { - fn backend(&self) -> &Backend { + pub fn block_producer(&self) -> &BlockProducer { + &self.block_producer + } + + pub fn backend(&self) -> &Backend { &self.backend } - async fn state(&self, block_id: &BlockId) -> SequencerResult { + pub async fn state(&self, block_id: &BlockId) -> SequencerResult { match block_id { - BlockId::Tag(BlockTag::Latest) => Ok(self.backend.state.read().await.clone()), + BlockId::Tag(BlockTag::Latest) => Ok(self.backend.state.read().await.as_ref_db()), BlockId::Tag(BlockTag::Pending) => { - self.backend.pending_state().await.ok_or(SequencerError::StateNotFound(*block_id)) + if let Some(state) = self.pending_state() { + Ok(state.state.read().as_ref_db()) + } else { + Ok(self.backend.state.read().await.as_ref_db()) + } } _ => { - if let Some(hash) = self.backend.storage.read().await.block_hash(*block_id) { + if let Some(hash) = self.backend.blockchain.block_hash(*block_id) { self.backend .states .read() @@ -207,57 +122,46 @@ impl Sequencer for KatanaSequencer { } } - async fn add_deploy_account_transaction( + pub async fn add_deploy_account_transaction( &self, transaction: DeployAccountTransaction, ) -> (FieldElement, FieldElement) { let transaction_hash = transaction.inner.transaction_hash.0.into(); let contract_address = transaction.contract_address; - self.backend.handle_transaction(Transaction::DeployAccount(transaction)).await; + self.pool.add_transaction(Transaction::DeployAccount(transaction)); (transaction_hash, contract_address) } - async fn add_declare_transaction(&self, transaction: DeclareTransaction) { - self.backend.handle_transaction(Transaction::Declare(transaction)).await; + pub fn add_declare_transaction(&self, transaction: DeclareTransaction) { + self.pool.add_transaction(Transaction::Declare(transaction)) } - async fn add_invoke_transaction(&self, transaction: InvokeTransaction) { - self.backend.handle_transaction(Transaction::Invoke(transaction)).await; + pub fn add_invoke_transaction(&self, transaction: InvokeTransaction) { + self.pool.add_transaction(Transaction::Invoke(transaction)) } - async fn estimate_fee( + pub async fn estimate_fee( &self, - account_transaction: AccountTransaction, + transactions: Vec, block_id: BlockId, - ) -> SequencerResult { - if self.block(block_id).await.is_none() { - return Err(SequencerError::BlockNotFound(block_id)); - } - + ) -> SequencerResult> { let state = self.state(&block_id).await?; - - self.backend - .estimate_fee(account_transaction, state) - .map_err(SequencerError::TransactionExecution) + self.backend.estimate_fee(transactions, state).map_err(SequencerError::TransactionExecution) } - async fn block_hash_and_number(&self) -> (FieldElement, u64) { - let hash = self.backend.storage.read().await.latest_hash; - let number = self.backend.storage.read().await.latest_number; + pub async fn block_hash_and_number(&self) -> (FieldElement, u64) { + let hash = self.backend.blockchain.storage.read().latest_hash; + let number = self.backend.blockchain.storage.read().latest_number; (hash, number) } - async fn class_hash_at( + pub async fn class_hash_at( &self, block_id: BlockId, contract_address: ContractAddress, ) -> SequencerResult { - if self.block(block_id).await.is_none() { - return Err(SequencerError::BlockNotFound(block_id)); - } - if !self.verify_contract_exists(&contract_address).await { return Err(SequencerError::ContractNotFound(contract_address)); } @@ -266,36 +170,31 @@ impl Sequencer for KatanaSequencer { state.get_class_hash_at(contract_address).map_err(SequencerError::State) } - async fn class( + pub async fn class( &self, block_id: BlockId, class_hash: ClassHash, ) -> SequencerResult { - if self.block(block_id).await.is_none() { - return Err(SequencerError::BlockNotFound(block_id)); - } - let mut state = self.state(&block_id).await?; - match state.get_compiled_contract_class(&class_hash).map_err(SequencerError::State)? { - ContractClass::V0(c) => Ok(StarknetContract::Legacy(c)), - ContractClass::V1(_) => state + if let ContractClass::V0(c) = + state.get_compiled_contract_class(&class_hash).map_err(SequencerError::State)? + { + Ok(StarknetContract::Legacy(c)) + } else { + state .get_sierra_class(&class_hash) .map(StarknetContract::Sierra) - .map_err(SequencerError::State), + .map_err(SequencerError::State) } } - async fn storage_at( + pub async fn storage_at( &self, contract_address: ContractAddress, storage_key: StorageKey, block_id: BlockId, ) -> SequencerResult { - if self.block(block_id).await.is_none() { - return Err(SequencerError::BlockNotFound(block_id)); - } - if !self.verify_contract_exists(&contract_address).await { return Err(SequencerError::ContractNotFound(contract_address)); } @@ -304,42 +203,66 @@ impl Sequencer for KatanaSequencer { state.get_storage_at(contract_address, storage_key).map_err(SequencerError::State) } - async fn chain_id(&self) -> ChainId { + pub async fn chain_id(&self) -> ChainId { self.backend.env.read().block.chain_id.clone() } - async fn block_number(&self) -> u64 { - self.backend.storage.read().await.latest_number + pub async fn block_number(&self) -> u64 { + self.backend.blockchain.storage.read().latest_number } - async fn block(&self, block_id: BlockId) -> Option { + pub async fn block(&self, block_id: BlockId) -> Option { + let block_id = match block_id { + BlockId::Tag(BlockTag::Pending) if self.block_producer.is_instant_mining() => { + BlockId::Tag(BlockTag::Latest) + } + _ => block_id, + }; + match block_id { BlockId::Tag(BlockTag::Pending) => { - self.backend.pending_block.read().await.as_ref().map(|b| b.as_block().into()) - } - BlockId::Tag(BlockTag::Latest) => { - let latest_hash = self.backend.storage.read().await.latest_hash; - self.backend.storage.read().await.blocks.get(&latest_hash).map(|b| b.clone().into()) - } - BlockId::Hash(hash) => { - self.backend.storage.read().await.blocks.get(&hash).map(|b| b.clone().into()) + let state = self.pending_state().expect("pending state should exist"); + + let block_context = self.backend.env.read().block.clone(); + let latest_hash = self.backend.blockchain.storage.read().latest_hash; + + let header = PartialHeader { + parent_hash: latest_hash, + gas_price: block_context.gas_price, + number: block_context.block_number.0, + timestamp: block_context.block_timestamp.0, + sequencer_address: (*block_context.sequencer_address.0.key()).into(), + }; + + let (transactions, outputs) = { + state + .executed_transactions + .read() + .iter() + .filter_map(|tx| match tx { + MaybeInvalidExecutedTransaction::Valid(tx) => { + Some((tx.clone(), tx.output.clone())) + } + _ => None, + }) + .unzip() + }; + + Some(ExecutedBlock::Pending(PartialBlock { header, transactions, outputs })) } - BlockId::Number(num) => { - let hash = *self.backend.storage.read().await.hashes.get(&num)?; - self.backend.storage.read().await.blocks.get(&hash).map(|b| b.clone().into()) + + _ => { + let hash = self.backend.blockchain.block_hash(block_id)?; + self.backend.blockchain.storage.read().blocks.get(&hash).map(|b| b.clone().into()) } } } - async fn nonce_at( + pub async fn nonce_at( &self, block_id: BlockId, contract_address: ContractAddress, ) -> SequencerResult { - if self.block(block_id).await.is_none() { - return Err(SequencerError::BlockNotFound(block_id)); - } - if !self.verify_contract_exists(&contract_address).await { return Err(SequencerError::ContractNotFound(contract_address)); } @@ -348,15 +271,11 @@ impl Sequencer for KatanaSequencer { state.get_nonce_at(contract_address).map_err(SequencerError::State) } - async fn call( + pub async fn call( &self, block_id: BlockId, function_call: ExternalFunctionCall, ) -> SequencerResult> { - if self.block(block_id).await.is_none() { - return Err(SequencerError::BlockNotFound(block_id)); - } - if !self.verify_contract_exists(&function_call.contract_address).await { return Err(SequencerError::ContractNotFound(function_call.contract_address)); } @@ -369,21 +288,7 @@ impl Sequencer for KatanaSequencer { .map(|execution_info| execution_info.execution.retdata.0) } - async fn transaction_status(&self, hash: &FieldElement) -> Option { - match self.backend.storage.read().await.transactions.get(hash) { - Some(tx) => Some(tx.status()), - // If the requested transaction is not available in the storage then - // check if it is available in the pending block. - None => self.backend.pending_block.read().await.as_ref().and_then(|b| { - b.transactions - .iter() - .find(|tx| tx.inner.hash() == *hash) - .map(|_| TransactionStatus::AcceptedOnL2) - }), - } - } - - async fn transaction_receipt( + pub async fn transaction_receipt( &self, hash: &FieldElement, ) -> Option { @@ -400,21 +305,27 @@ impl Sequencer for KatanaSequencer { } } - async fn transaction(&self, hash: &FieldElement) -> Option { - match self.backend.storage.read().await.transactions.get(hash) { - Some(tx) => Some(tx.clone()), + pub async fn transaction(&self, hash: &FieldElement) -> Option { + let tx = self.backend.blockchain.storage.read().transactions.get(hash).cloned(); + match tx { + Some(tx) => Some(tx), // If the requested transaction is not available in the storage then // check if it is available in the pending block. - None => self.backend.pending_block.read().await.as_ref().and_then(|b| { - b.transactions - .iter() - .find(|tx| tx.inner.hash() == *hash) - .map(|tx| PendingTransaction(tx.clone()).into()) + None => self.pending_state().as_ref().and_then(|state| { + state.executed_transactions.read().iter().find_map(|tx| match tx { + MaybeInvalidExecutedTransaction::Valid(tx) if tx.inner.hash() == *hash => { + Some(PendingTransaction(tx.clone()).into()) + } + MaybeInvalidExecutedTransaction::Invalid(tx) if tx.inner.hash() == *hash => { + Some(tx.as_ref().clone().into()) + } + _ => None, + }) }), } } - async fn events( + pub async fn events( &self, from_block: BlockId, to_block: BlockId, @@ -426,16 +337,16 @@ impl Sequencer for KatanaSequencer { let mut current_block = 0; let (mut from_block, to_block) = { - let storage = self.backend.storage.read().await; + let storage = &self.backend.blockchain; let from = storage .block_hash(from_block) - .and_then(|hash| storage.blocks.get(&hash).map(|b| b.header.number)) + .and_then(|hash| storage.storage.read().blocks.get(&hash).map(|b| b.header.number)) .ok_or(SequencerError::BlockNotFound(from_block))?; let to = storage .block_hash(to_block) - .and_then(|hash| storage.blocks.get(&hash).map(|b| b.header.number)) + .and_then(|hash| storage.storage.read().blocks.get(&hash).map(|b| b.header.number)) .ok_or(SequencerError::BlockNotFound(to_block))?; (from, to) @@ -454,9 +365,9 @@ impl Sequencer for KatanaSequencer { for i in from_block..=to_block { let block = self .backend + .blockchain .storage .read() - .await .block_by_number(i) .cloned() .ok_or(SequencerError::BlockNotFound(BlockId::Number(i)))?; @@ -465,12 +376,12 @@ impl Sequencer for KatanaSequencer { // if the current block is the latest block then we use the latest hash let block_hash = self .backend + .blockchain .storage .read() - .await .block_by_number(i + 1) .map(|b| b.header.parent_hash) - .unwrap_or(self.backend.storage.read().await.latest_hash); + .unwrap_or(self.backend.blockchain.storage.read().latest_hash); let block_number = i; @@ -545,24 +456,63 @@ impl Sequencer for KatanaSequencer { Ok(EventsPage { events: filtered_events, continuation_token: None }) } - async fn state_update(&self, block_id: BlockId) -> SequencerResult { + pub async fn state_update(&self, block_id: BlockId) -> SequencerResult { let block_number = self .backend - .storage - .read() - .await + .blockchain .block_hash(block_id) .ok_or(SequencerError::BlockNotFound(block_id))?; self.backend + .blockchain .storage .read() - .await .state_update .get(&block_number) .cloned() .ok_or(SequencerError::StateUpdateNotFound(block_id)) } + + pub async fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), SequencerError> { + if self.has_pending_transactions().await { + return Err(SequencerError::PendingTransactions); + } + self.backend().block_context_generator.write().next_block_start_time = timestamp; + Ok(()) + } + + pub async fn increase_next_block_timestamp( + &self, + timestamp: u64, + ) -> Result<(), SequencerError> { + if self.has_pending_transactions().await { + return Err(SequencerError::PendingTransactions); + } + self.backend().block_context_generator.write().block_timestamp_offset += timestamp as i64; + Ok(()) + } + + pub async fn has_pending_transactions(&self) -> bool { + if let Some(ref pending) = self.pending_state() { + !pending.executed_transactions.read().is_empty() + } else { + false + } + } + + pub async fn set_storage_at( + &self, + contract_address: ContractAddress, + storage_key: StorageKey, + value: StarkFelt, + ) -> Result<(), SequencerError> { + if let Some(ref pending) = self.pending_state() { + pending.state.write().set_storage_at(contract_address, storage_key, value); + } else { + self.backend().state.write().await.set_storage_at(contract_address, storage_key, value); + } + Ok(()) + } } fn filter_events_by_params( diff --git a/crates/katana/core/src/service/block_producer.rs b/crates/katana/core/src/service/block_producer.rs new file mode 100644 index 0000000000..807e449866 --- /dev/null +++ b/crates/katana/core/src/service/block_producer.rs @@ -0,0 +1,400 @@ +use std::collections::{HashMap, VecDeque}; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; + +use blockifier::state::state_api::{State, StateReader}; +use futures::stream::{Stream, StreamExt}; +use futures::FutureExt; +use parking_lot::RwLock; +use tokio::time::{interval_at, Instant, Interval}; +use tracing::trace; + +use crate::backend::storage::transaction::{RejectedTransaction, Transaction}; +use crate::backend::Backend; +use crate::db::cached::CachedStateWrapper; +use crate::db::StateRefDb; +use crate::execution::{ + create_execution_outcome, ExecutedTransaction, ExecutionOutcome, + MaybeInvalidExecutedTransaction, PendingState, TransactionExecutor, +}; + +pub struct MinedBlockOutcome { + pub block_number: u64, + pub transactions: Vec, +} + +type ServiceFuture = Pin + Send + Sync>>; +type InstantBlockMiningFuture = ServiceFuture; +type IntervalBlockMiningFuture = ServiceFuture; + +/// The type which responsible for block production. +#[must_use = "BlockProducer does nothing unless polled"] +#[derive(Clone)] +pub struct BlockProducer { + /// The inner mode of mining. + pub inner: Arc>, +} + +impl BlockProducer { + /// Creates a block producer that mines a new block every `interval` milliseconds. + pub fn interval(backend: Arc, initial_state: StateRefDb, interval: u64) -> Self { + Self { + inner: Arc::new(RwLock::new(BlockProducerMode::Interval(IntervalBlockProducer::new( + backend, + initial_state, + interval, + )))), + } + } + + /// Creates a new block producer that will only be possible to mine by calling the + /// `katana_generateBlock` RPC method. + pub fn on_demand(backend: Arc, initial_state: StateRefDb) -> Self { + Self { + inner: Arc::new(RwLock::new(BlockProducerMode::Interval( + IntervalBlockProducer::new_no_mining(backend, initial_state), + ))), + } + } + + /// Creates a block producer that mines a new block as soon as there are ready transactions in + /// the transactions pool. + pub fn instant(backend: Arc) -> Self { + Self { + inner: Arc::new(RwLock::new(BlockProducerMode::Instant(InstantBlockProducer::new( + backend, + )))), + } + } + + pub(super) fn queue(&self, transactions: Vec) { + let mut mode = self.inner.write(); + match &mut *mode { + BlockProducerMode::Instant(producer) => producer.queued.push_back(transactions), + BlockProducerMode::Interval(producer) => producer.queued.push_back(transactions), + } + } + + /// Returns `true` if the block producer is running in _interval_ mode. Otherwise, `fales`. + pub fn is_interval_mining(&self) -> bool { + matches!(*self.inner.read(), BlockProducerMode::Interval(_)) + } + + /// Returns `true` if the block producer is running in _instant_ mode. Otherwise, `fales`. + pub fn is_instant_mining(&self) -> bool { + matches!(*self.inner.read(), BlockProducerMode::Instant(_)) + } + + // Handler for the `katana_generateBlock` RPC method. + pub fn force_mine(&self) { + trace!(target: "miner", "force mining"); + let mut mode = self.inner.write(); + match &mut *mode { + BlockProducerMode::Instant(producer) => { + tokio::task::block_in_place(|| futures::executor::block_on(producer.force_mine())) + } + BlockProducerMode::Interval(producer) => { + tokio::task::block_in_place(|| futures::executor::block_on(producer.force_mine())) + } + } + } +} + +impl Stream for BlockProducer { + type Item = MinedBlockOutcome; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut mode = self.inner.write(); + match &mut *mode { + BlockProducerMode::Instant(producer) => producer.poll_next_unpin(cx), + BlockProducerMode::Interval(producer) => producer.poll_next_unpin(cx), + } + } +} + +/// The inner type of [BlockProducer]. +/// +/// On _interval_ mining, a new block is opened for a fixed amount of interval. Within this +/// interval, it executes all the queued transactions and keep hold of the pending state after +/// executing all the transactions. Once the interval is over, the block producer will close/mine +/// the block with all the transactions that have been executed within the interval and applies the +/// resulting state to the latest state. Then, a new block is opened for the next interval. As such, +/// the block context is updated only when a new block is opened. +/// +/// On _instant_ mining, a new block is mined as soon as there are transactions in the tx pool. The +/// block producer will execute all the transactions in the mempool and mine a new block with the +/// resulting state. The block context is only updated every time a new block is mined as opposed to +/// updating it when the block is opened (in _interval_ mode). +pub enum BlockProducerMode { + Interval(IntervalBlockProducer), + Instant(InstantBlockProducer), +} + +pub struct IntervalBlockProducer { + /// The interval at which new blocks are mined. + interval: Option, + backend: Arc, + /// Single active future that mines a new block + block_mining: Option, + /// Backlog of sets of transactions ready to be mined + queued: VecDeque>, + /// The state of the pending block after executing all the transactions within the interval. + state: Arc, + /// This is to make sure that the block context is updated + /// before the first block is opened. + is_initialized: bool, +} + +impl IntervalBlockProducer { + pub fn new(backend: Arc, db: StateRefDb, interval: u64) -> Self { + let interval = { + let duration = Duration::from_millis(interval); + let mut interval = interval_at(Instant::now() + duration, duration); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + interval + }; + + let state = Arc::new(PendingState { + state: RwLock::new(CachedStateWrapper::new(db)), + executed_transactions: Default::default(), + }); + + Self { + state, + backend, + block_mining: None, + is_initialized: false, + interval: Some(interval), + queued: VecDeque::default(), + } + } + + /// Creates a new [IntervalBlockProducer] with no `interval`. This mode will not produce blocks + /// for every fixed interval, although it will still execute all queued transactions and + /// keep hold of the pending state. + pub fn new_no_mining(backend: Arc, db: StateRefDb) -> Self { + let state = Arc::new(PendingState { + state: RwLock::new(CachedStateWrapper::new(db)), + executed_transactions: Default::default(), + }); + + Self { + state, + backend, + interval: None, + block_mining: None, + is_initialized: false, + queued: VecDeque::default(), + } + } + + pub fn state(&self) -> Arc { + self.state.clone() + } + + /// Force mine a new block. It will only able to mine if there is no ongoing mining process. + pub async fn force_mine(&self) { + if self.block_mining.is_none() { + let outcome = self.outcome(); + let _ = Self::do_mine(outcome, self.backend.clone(), self.state.clone()).await; + } else { + trace!(target: "miner", "unable to force mine while a mining process is running") + } + } + + async fn do_mine( + execution_outcome: ExecutionOutcome, + backend: Arc, + pending_state: Arc, + ) -> MinedBlockOutcome { + trace!(target: "miner", "creating new block"); + let (outcome, new_state) = backend.mine_pending_block(execution_outcome).await; + trace!(target: "miner", "created new block: {}", outcome.block_number); + + backend.update_block_context(); + // reset the state for the next block + pending_state.executed_transactions.write().clear(); + *pending_state.state.write() = CachedStateWrapper::new(new_state); + + outcome + } + + fn execute_transactions(&self, transactions: Vec) { + let transactions = { + let mut state = self.state.state.write(); + TransactionExecutor::new( + &mut state, + &self.backend.env.read().block, + !self.backend.config.read().disable_fee, + ) + .with_error_log() + .with_events_log() + .with_resources_log() + .execute_many(transactions.clone()) + .into_iter() + .zip(transactions) + .map(|(res, tx)| match res { + Ok(exec_info) => { + let executed_tx = ExecutedTransaction::new(tx, exec_info); + MaybeInvalidExecutedTransaction::Valid(Arc::new(executed_tx)) + } + Err(err) => { + let rejected_tx = + RejectedTransaction { inner: tx, execution_error: err.to_string() }; + MaybeInvalidExecutedTransaction::Invalid(Arc::new(rejected_tx)) + } + }) + .collect::>() + }; + + self.state.executed_transactions.write().extend(transactions); + } + + fn outcome(&self) -> ExecutionOutcome { + let state = &mut self.state.state.write(); + + let declared_sierra_classes = state.sierra_class().clone(); + let state_diff = state.to_state_diff(); + let declared_classes = state_diff + .class_hash_to_compiled_class_hash + .iter() + .map(|(class_hash, _)| { + let contract_class = state + .get_compiled_contract_class(class_hash) + .expect("contract class must exist in state if declared"); + (*class_hash, contract_class) + }) + .collect::>(); + + ExecutionOutcome { + state_diff, + declared_classes, + declared_sierra_classes, + transactions: self.state.executed_transactions.read().clone(), + } + } +} + +impl Stream for IntervalBlockProducer { + // mined block outcome and the new state + type Item = MinedBlockOutcome; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let pin = self.get_mut(); + + if !pin.is_initialized { + pin.backend.update_block_context(); + pin.is_initialized = true; + } + + if let Some(interval) = &mut pin.interval { + if interval.poll_tick(cx).is_ready() && pin.block_mining.is_none() { + pin.block_mining = Some(Box::pin(Self::do_mine( + pin.outcome(), + pin.backend.clone(), + pin.state.clone(), + ))); + } + } + + // only execute transactions if there is no mining in progress + if !pin.queued.is_empty() && pin.block_mining.is_none() { + let transactions = pin.queued.pop_front().expect("not empty; qed"); + pin.execute_transactions(transactions); + } + + // poll the mining future + if let Some(mut mining) = pin.block_mining.take() { + // reset the executor for the next block + if let Poll::Ready(outcome) = mining.poll_unpin(cx) { + return Poll::Ready(Some(outcome)); + } else { + pin.block_mining = Some(mining) + } + } + + Poll::Pending + } +} + +pub struct InstantBlockProducer { + /// Holds the backend if no block is being mined + backend: Arc, + /// Single active future that mines a new block + block_mining: Option, + /// Backlog of sets of transactions ready to be mined + queued: VecDeque>, +} + +impl InstantBlockProducer { + pub fn new(backend: Arc) -> Self { + Self { backend, block_mining: None, queued: VecDeque::default() } + } + + pub async fn force_mine(&mut self) { + if self.block_mining.is_none() { + let txs = self.queued.pop_front().unwrap_or_default(); + let _ = Self::do_mine(self.backend.clone(), txs).await; + } else { + trace!(target: "miner", "unable to force mine while a mining process is running") + } + } + + async fn do_mine(backend: Arc, transactions: Vec) -> MinedBlockOutcome { + trace!(target: "miner", "creating new block"); + + backend.update_block_context(); + + let mut state = CachedStateWrapper::new(backend.state.read().await.as_ref_db()); + let block_context = backend.env.read().block.clone(); + + let results = TransactionExecutor::new( + &mut state, + &block_context, + !backend.config.read().disable_fee, + ) + .with_error_log() + .with_events_log() + .with_resources_log() + .execute_many(transactions.clone()); + + let outcome = backend + .do_mine_block(create_execution_outcome( + &mut state, + transactions.into_iter().zip(results).collect(), + )) + .await; + + trace!(target: "miner", "created new block: {}", outcome.block_number); + + outcome + } +} + +impl Stream for InstantBlockProducer { + // mined block outcome and the new state + type Item = MinedBlockOutcome; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let pin = self.get_mut(); + + if !pin.queued.is_empty() && pin.block_mining.is_none() { + let transactions = pin.queued.pop_front().expect("not empty; qed"); + pin.block_mining = Some(Box::pin(Self::do_mine(pin.backend.clone(), transactions))); + } + + // poll the mining future + if let Some(mut mining) = pin.block_mining.take() { + println!("ohayo"); + if let Poll::Ready(outcome) = mining.poll_unpin(cx) { + return Poll::Ready(Some(outcome)); + } else { + pin.block_mining = Some(mining) + } + } + + Poll::Pending + } +} diff --git a/crates/katana/core/src/service/mod.rs b/crates/katana/core/src/service/mod.rs new file mode 100644 index 0000000000..3460640f9c --- /dev/null +++ b/crates/katana/core/src/service/mod.rs @@ -0,0 +1,107 @@ +// Code adapted from Foundry's Anvil + +//! background service + +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use futures::channel::mpsc::Receiver; +use futures::stream::{Fuse, Stream, StreamExt}; +use starknet::core::types::FieldElement; +use tracing::trace; + +use self::block_producer::BlockProducer; +use crate::backend::storage::transaction::Transaction; +use crate::pool::TransactionPool; + +pub mod block_producer; + +/// The type that drives the blockchain's state +/// +/// This service is basically an endless future that continuously polls the miner which returns +/// transactions for the next block, then those transactions are handed off to the [BlockProducer] +/// to construct a new block. +pub struct NodeService { + /// the pool that holds all transactions + pool: Arc, + /// creates new blocks + block_producer: BlockProducer, + /// the miner responsible to select transactions from the `pool´ + miner: TransactionMiner, +} + +impl NodeService { + pub fn new( + pool: Arc, + miner: TransactionMiner, + block_producer: BlockProducer, + ) -> Self { + Self { pool, block_producer, miner } + } +} + +impl Future for NodeService { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pin = self.get_mut(); + + // this drives block production and feeds new sets of ready transactions to the block + // producer + loop { + while let Poll::Ready(Some(outcome)) = pin.block_producer.poll_next_unpin(cx) { + trace!(target: "node", "mined block {}", outcome.block_number); + } + + if let Poll::Ready(transactions) = pin.miner.poll(&pin.pool, cx) { + // miner returned a set of transaction that we feed to the producer + pin.block_producer.queue(transactions); + } else { + // no progress made + break; + } + } + + Poll::Pending + } +} + +/// The type which takes the transaction from the pool and feeds them to the block producer. +pub struct TransactionMiner { + /// stores whether there are pending transacions (if known) + has_pending_txs: Option, + /// Receives hashes of transactions that are ready from the pool + rx: Fuse>, +} + +impl TransactionMiner { + pub fn new(rx: Receiver) -> Self { + Self { rx: rx.fuse(), has_pending_txs: None } + } + + fn poll( + &mut self, + pool: &Arc, + cx: &mut Context<'_>, + ) -> Poll> { + // drain the notification stream + while let Poll::Ready(Some(_)) = Pin::new(&mut self.rx).poll_next(cx) { + self.has_pending_txs = Some(true); + } + + if self.has_pending_txs == Some(false) { + return Poll::Pending; + } + + // take all the transactions from the pool + let transactions = pool.get_transactions(); + + if transactions.is_empty() { + return Poll::Pending; + } + + Poll::Ready(transactions) + } +} diff --git a/crates/katana/core/src/utils/contract.rs b/crates/katana/core/src/utils/contract.rs index f2791117b5..df2282158e 100644 --- a/crates/katana/core/src/utils/contract.rs +++ b/crates/katana/core/src/utils/contract.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::io::Read; +use std::io::{Read, Write}; use anyhow::{anyhow, Result}; use blockifier::execution::contract_class::{ @@ -9,6 +9,7 @@ use cairo_lang_starknet::casm_contract_class::CasmContractClass; use cairo_vm::serde::deserialize_program::ProgramJson; use serde_json::json; use starknet::core::types::contract::legacy::{LegacyContractClass, LegacyProgram}; +use starknet::core::types::contract::CompiledClass; use starknet::core::types::{ CompressedLegacyContractClass, ContractClass, FieldElement, FlattenedSierraClass, LegacyContractEntryPoint, LegacyEntryPointsByType, @@ -51,9 +52,19 @@ pub fn rpc_to_inner_class( contract_class: &FlattenedSierraClass, ) -> Result<(FieldElement, InnerContractClass)> { let class_hash = contract_class.class_hash(); + let contract_class = rpc_to_cairo_contract_class(contract_class)?; + let casm_contract = CasmContractClass::from_contract_class(contract_class, true)?; + Ok((class_hash, InnerContractClass::V1(casm_contract.try_into()?))) +} +/// Converts `starknet-rs` RPC [FlattenedSierraClass] type to Cairo's +/// [ContractClass](cairo_lang_starknet::contract_class::ContractClass) type. +pub fn rpc_to_cairo_contract_class( + contract_class: &FlattenedSierraClass, +) -> Result { let value = serde_json::to_value(contract_class)?; - let contract_class = cairo_lang_starknet::contract_class::ContractClass { + + Ok(cairo_lang_starknet::contract_class::ContractClass { abi: serde_json::from_value(value["abi"].clone()).ok(), sierra_program: serde_json::from_value(value["sierra_program"].clone())?, entry_points_by_type: serde_json::from_value(value["entry_points_by_type"].clone())?, @@ -62,10 +73,18 @@ pub fn rpc_to_inner_class( value["sierra_program_debug_info"].clone(), ) .ok(), - }; + }) +} +/// Compute the compiled class hash from the given [FlattenedSierraClass]. +pub fn compiled_class_hash_from_flattened_sierra_class( + contract_class: &FlattenedSierraClass, +) -> Result { + let contract_class = rpc_to_cairo_contract_class(contract_class)?; let casm_contract = CasmContractClass::from_contract_class(contract_class, true)?; - Ok((class_hash, InnerContractClass::V1(casm_contract.try_into()?))) + let res = serde_json::to_string_pretty(&casm_contract)?; + let compiled_class: CompiledClass = serde_json::from_str(&res)?; + Ok(compiled_class.class_hash()?) } pub fn legacy_rpc_to_inner_class( @@ -116,7 +135,7 @@ fn to_rpc_legacy_entry_points_by_type( /// Returns a compressed vector of bytes fn compress(data: &[u8]) -> Result> { let mut gzip_encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()); - serde_json::to_writer(&mut gzip_encoder, data)?; + gzip_encoder.write_all(data)?; Ok(gzip_encoder.finish()?) } diff --git a/crates/katana/core/src/utils/transaction.rs b/crates/katana/core/src/utils/transaction.rs index 77dd4afcf6..8a2c8c5f9f 100644 --- a/crates/katana/core/src/utils/transaction.rs +++ b/crates/katana/core/src/utils/transaction.rs @@ -11,9 +11,7 @@ use starknet::core::types::{ InvokeTransactionV0, InvokeTransactionV1, L1HandlerTransaction, Transaction as RpcTransaction, }; use starknet::core::utils::get_contract_address; -use starknet_api::core::{ - ClassHash, CompiledClassHash, ContractAddress, EntryPointSelector, Nonce, PatriciaKey, -}; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; use starknet_api::hash::{StarkFelt, StarkHash}; use starknet_api::transaction::{ Calldata, ContractAddressSalt, DeclareTransaction as DeclareApiTransaction, @@ -21,9 +19,8 @@ use starknet_api::transaction::{ DeclareTransactionV2 as DeclareApiTransactionV2, DeployAccountTransaction as DeployAccountApiTransaction, DeployTransaction as DeployApiTransaction, Fee, InvokeTransaction as InvokeApiTransaction, - InvokeTransactionV0 as InvokeApiTransactionV0, InvokeTransactionV1 as InvokeApiTransactionV1, - L1HandlerTransaction as L1HandlerApiTransaction, Transaction as ApiTransaction, - TransactionHash, TransactionSignature, TransactionVersion, + InvokeTransactionV1 as InvokeApiTransactionV1, L1HandlerTransaction as L1HandlerApiTransaction, + Transaction as ApiTransaction, TransactionHash, TransactionSignature, TransactionVersion, }; use starknet_api::{patricia_key, stark_felt}; @@ -31,6 +28,14 @@ use super::contract::rpc_to_inner_class; use crate::utils::contract::legacy_rpc_to_inner_class; use crate::utils::starkfelt_to_u128; +/// 2^ 128 +const QUERY_VERSION_OFFSET: FieldElement = FieldElement::from_mont([ + 18446744073700081665, + 17407, + 18446744073709551584, + 576460752142434320, +]); + /// Cairo string for "invoke" const PREFIX_INVOKE: FieldElement = FieldElement::from_mont([ 18443034532770911073, @@ -56,6 +61,7 @@ const PREFIX_DEPLOY_ACCOUNT: FieldElement = FieldElement::from_mont([ ]); /// Compute the hash of a V1 DeployAccount transaction. +#[allow(clippy::too_many_arguments)] pub fn compute_deploy_account_v1_transaction_hash( contract_address: FieldElement, constructor_calldata: &[FieldElement], @@ -64,12 +70,13 @@ pub fn compute_deploy_account_v1_transaction_hash( max_fee: FieldElement, chain_id: FieldElement, nonce: FieldElement, + is_query: bool, ) -> FieldElement { let calldata_to_hash = [&[class_hash, salt], constructor_calldata].concat(); compute_hash_on_elements(&[ PREFIX_DEPLOY_ACCOUNT, - FieldElement::ONE, // version + if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ contract_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(&calldata_to_hash), @@ -86,10 +93,11 @@ pub fn compute_declare_v1_transaction_hash( max_fee: FieldElement, chain_id: FieldElement, nonce: FieldElement, + is_query: bool, ) -> FieldElement { compute_hash_on_elements(&[ PREFIX_DECLARE, - FieldElement::ONE, // version + if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ sender_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(&[class_hash]), @@ -107,10 +115,11 @@ pub fn compute_declare_v2_transaction_hash( chain_id: FieldElement, nonce: FieldElement, compiled_class_hash: FieldElement, + is_query: bool, ) -> FieldElement { compute_hash_on_elements(&[ PREFIX_DECLARE, - FieldElement::TWO, // version + if is_query { QUERY_VERSION_OFFSET + FieldElement::TWO } else { FieldElement::TWO }, /* version */ sender_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(&[class_hash]), @@ -128,10 +137,11 @@ pub fn compute_invoke_v0_transaction_hash( calldata: &[FieldElement], max_fee: FieldElement, chain_id: FieldElement, + is_query: bool, ) -> FieldElement { compute_hash_on_elements(&[ PREFIX_INVOKE, - FieldElement::ZERO, // version + if is_query { QUERY_VERSION_OFFSET + FieldElement::ZERO } else { FieldElement::ZERO }, /* version */ contract_address, entry_point_selector, // entry_point_selector compute_hash_on_elements(calldata), @@ -147,10 +157,11 @@ pub fn compute_invoke_v1_transaction_hash( max_fee: FieldElement, chain_id: FieldElement, nonce: FieldElement, + is_query: bool, ) -> FieldElement { compute_hash_on_elements(&[ PREFIX_INVOKE, - FieldElement::ONE, // version + if is_query { QUERY_VERSION_OFFSET + FieldElement::ONE } else { FieldElement::ONE }, /* version */ sender_address, FieldElement::ZERO, // entry_point_selector compute_hash_on_elements(calldata), @@ -237,7 +248,6 @@ fn api_deploy_account_to_rpc_transaction( fn api_invoke_to_rpc_transaction(transaction: InvokeApiTransaction) -> InvokeTransaction { match transaction { InvokeApiTransaction::V0(tx) => InvokeTransaction::V0(InvokeTransactionV0 { - nonce: FieldElement::ZERO, max_fee: tx.max_fee.0.into(), transaction_hash: tx.transaction_hash.0.into(), contract_address: (*tx.contract_address.0.key()).into(), @@ -298,55 +308,29 @@ pub fn broadcasted_invoke_rpc_to_api_transaction( transaction: BroadcastedInvokeTransaction, chain_id: FieldElement, ) -> InvokeApiTransaction { - match transaction { - BroadcastedInvokeTransaction::V0(tx) => { - let transaction_hash = compute_invoke_v0_transaction_hash( - tx.contract_address, - tx.entry_point_selector, - &tx.calldata, - tx.max_fee, - chain_id, - ); - - let transaction = InvokeApiTransactionV0 { - transaction_hash: TransactionHash(transaction_hash.into()), - contract_address: ContractAddress(patricia_key!(tx.contract_address)), - entry_point_selector: EntryPointSelector(tx.entry_point_selector.into()), - calldata: Calldata(Arc::new(tx.calldata.into_iter().map(|c| c.into()).collect())), - max_fee: Fee(starkfelt_to_u128(tx.max_fee.into()) - .expect("convert max fee StarkFelt to u128")), - signature: TransactionSignature( - tx.signature.into_iter().map(|e| e.into()).collect(), - ), - }; - - InvokeApiTransaction::V0(transaction) - } + let BroadcastedInvokeTransaction { + calldata, max_fee, nonce, sender_address, signature, .. + } = transaction; - BroadcastedInvokeTransaction::V1(tx) => { - let transaction_hash = compute_invoke_v1_transaction_hash( - tx.sender_address, - &tx.calldata, - tx.max_fee, - chain_id, - tx.nonce, - ); + let hash = compute_invoke_v1_transaction_hash( + sender_address, + &calldata, + max_fee, + chain_id, + nonce, + transaction.is_query, + ); - let transaction = InvokeApiTransactionV1 { - transaction_hash: TransactionHash(transaction_hash.into()), - sender_address: ContractAddress(patricia_key!(tx.sender_address)), - nonce: Nonce(StarkFelt::from(tx.nonce)), - calldata: Calldata(Arc::new(tx.calldata.into_iter().map(|c| c.into()).collect())), - max_fee: Fee(starkfelt_to_u128(tx.max_fee.into()) - .expect("convert max fee StarkFelt to u128")), - signature: TransactionSignature( - tx.signature.into_iter().map(|e| e.into()).collect(), - ), - }; + let transaction = InvokeApiTransactionV1 { + nonce: Nonce(nonce.into()), + transaction_hash: TransactionHash(hash.into()), + sender_address: ContractAddress(patricia_key!(sender_address)), + signature: TransactionSignature(signature.into_iter().map(|e| e.into()).collect()), + calldata: Calldata(Arc::new(calldata.into_iter().map(|c| c.into()).collect())), + max_fee: Fee(starkfelt_to_u128(max_fee.into()).expect("convert max fee StarkFelt to u128")), + }; - InvokeApiTransaction::V1(transaction) - } - } + InvokeApiTransaction::V1(transaction) } /// Convert broadcasted Declare transaction type from `starknet-rs` to `starknet_api`'s @@ -367,6 +351,7 @@ pub fn broadcasted_declare_rpc_to_api_transaction( tx.max_fee, chain_id, tx.nonce, + tx.is_query, ); let transaction = DeclareApiTransactionV0V1 { @@ -394,6 +379,7 @@ pub fn broadcasted_declare_rpc_to_api_transaction( chain_id, tx.nonce, tx.compiled_class_hash, + tx.is_query, ); let transaction = DeclareApiTransactionV2 { @@ -447,6 +433,7 @@ pub fn broadcasted_deploy_account_rpc_to_api_transaction( max_fee, chain_id, nonce, + transaction.is_query, ); let api_transaction = DeployAccountApiTransaction { @@ -513,6 +500,7 @@ mod tests { max_fee, chain_id, nonce, + false, ); assert_eq!( diff --git a/crates/katana/core/tests/backend.rs b/crates/katana/core/tests/backend.rs new file mode 100644 index 0000000000..fcd1442c07 --- /dev/null +++ b/crates/katana/core/tests/backend.rs @@ -0,0 +1,38 @@ +use katana_core::backend::config::{Environment, StarknetConfig}; +use katana_core::backend::Backend; +use starknet_api::block::BlockNumber; + +fn create_test_starknet_config() -> StarknetConfig { + StarknetConfig { + seed: [0u8; 32], + total_accounts: 2, + disable_fee: true, + env: Environment::default(), + ..Default::default() + } +} + +async fn create_test_backend() -> Backend { + Backend::new(create_test_starknet_config()).await +} + +#[tokio::test] +async fn test_creating_blocks() { + let starknet = create_test_backend().await; + + assert_eq!(starknet.blockchain.storage.read().blocks.len(), 1); + assert_eq!(starknet.blockchain.storage.read().latest_number, 0); + + starknet.mine_empty_block().await; + starknet.mine_empty_block().await; + + assert_eq!(starknet.blockchain.storage.read().blocks.len(), 3); + assert_eq!(starknet.blockchain.storage.read().latest_number, 2); + assert_eq!(starknet.env.read().block.block_number, BlockNumber(2),); + + let block0 = starknet.blockchain.storage.read().block_by_number(0).unwrap().clone(); + let block1 = starknet.blockchain.storage.read().block_by_number(1).unwrap().clone(); + + assert_eq!(block0.header.number, 0); + assert_eq!(block1.header.number, 1); +} diff --git a/crates/katana/core/tests/sequencer.rs b/crates/katana/core/tests/sequencer.rs new file mode 100644 index 0000000000..ec8726027b --- /dev/null +++ b/crates/katana/core/tests/sequencer.rs @@ -0,0 +1,221 @@ +use std::time::Duration; + +use katana_core::backend::config::{Environment, StarknetConfig}; +use katana_core::backend::storage::transaction::{DeclareTransaction, KnownTransaction}; +use katana_core::sequencer::{KatanaSequencer, SequencerConfig}; +use katana_core::utils::contract::get_contract_class; +use starknet::core::types::FieldElement; +use starknet_api::core::{ClassHash, ContractAddress, Nonce, PatriciaKey}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::state::StorageKey; +use starknet_api::transaction::{ + DeclareTransaction as DeclareApiTransaction, DeclareTransactionV0V1, TransactionHash, +}; +use starknet_api::{patricia_key, stark_felt}; +use tokio::time::sleep; + +fn create_test_sequencer_config() -> (SequencerConfig, StarknetConfig) { + ( + SequencerConfig { block_time: None, ..Default::default() }, + StarknetConfig { + seed: [0u8; 32], + total_accounts: 2, + disable_fee: true, + env: Environment::default(), + ..Default::default() + }, + ) +} + +async fn create_test_sequencer() -> KatanaSequencer { + let (sequencer_config, starknet_config) = create_test_sequencer_config(); + KatanaSequencer::new(sequencer_config, starknet_config).await +} + +fn create_declare_transaction(sender_address: ContractAddress) -> DeclareTransaction { + let compiled_class = + get_contract_class(include_str!("../contracts/compiled/test_contract.json")); + DeclareTransaction { + inner: DeclareApiTransaction::V0(DeclareTransactionV0V1 { + class_hash: ClassHash(stark_felt!("0x1234")), + nonce: Nonce(1u8.into()), + sender_address, + transaction_hash: TransactionHash(stark_felt!("0x6969")), + ..Default::default() + }), + compiled_class, + sierra_class: None, + } +} + +#[tokio::test] +async fn test_next_block_timestamp_in_past() { + let sequencer = create_test_sequencer().await; + let block1 = sequencer.backend.mine_empty_block().await.block_number; + let block1_timestamp = sequencer + .backend + .blockchain + .storage + .read() + .block_by_number(block1) + .unwrap() + .header + .timestamp; + + sequencer.set_next_block_timestamp(block1_timestamp - 1000).await.unwrap(); + + let block2 = sequencer.backend.mine_empty_block().await.block_number; + let block2_timestamp = sequencer + .backend + .blockchain + .storage + .read() + .block_by_number(block2) + .unwrap() + .header + .timestamp; + + assert_eq!(block2_timestamp, block1_timestamp - 1000, "timestamp should be updated"); +} + +#[tokio::test] +async fn test_set_next_block_timestamp_in_future() { + let sequencer = create_test_sequencer().await; + let block1 = sequencer.backend.mine_empty_block().await.block_number; + let block1_timestamp = sequencer + .backend + .blockchain + .storage + .read() + .block_by_number(block1) + .unwrap() + .header + .timestamp; + + sequencer.set_next_block_timestamp(block1_timestamp + 1000).await.unwrap(); + + let block2 = sequencer.backend.mine_empty_block().await.block_number; + let block2_timestamp = sequencer + .backend + .blockchain + .storage + .read() + .block_by_number(block2) + .unwrap() + .header + .timestamp; + + assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); +} + +#[tokio::test] +async fn test_increase_next_block_timestamp() { + let sequencer = create_test_sequencer().await; + let block1 = sequencer.backend.mine_empty_block().await.block_number; + let block1_timestamp = sequencer + .backend + .blockchain + .storage + .read() + .block_by_number(block1) + .unwrap() + .header + .timestamp; + + sequencer.increase_next_block_timestamp(1000).await.unwrap(); + + let block2 = sequencer.backend.mine_empty_block().await.block_number; + let block2_timestamp = sequencer + .backend + .blockchain + .storage + .read() + .block_by_number(block2) + .unwrap() + .header + .timestamp; + + assert_eq!(block2_timestamp, block1_timestamp + 1000, "timestamp should be updated"); +} + +#[tokio::test] +async fn test_set_storage_at_on_instant_mode() { + let sequencer = create_test_sequencer().await; + sequencer.backend.mine_empty_block().await; + + let contract_address = ContractAddress(patricia_key!("0x1337")); + let key = StorageKey(patricia_key!("0x20")); + let val = stark_felt!("0xABC"); + + { + let mut state = sequencer.backend.state.write().await; + let read_val = state.get_storage_at(contract_address, key).unwrap(); + assert_eq!(stark_felt!("0x0"), read_val, "latest storage value should be 0"); + } + + sequencer.set_storage_at(contract_address, key, val).await.unwrap(); + + { + let mut state = sequencer.backend.state.write().await; + let read_val = state.get_storage_at(contract_address, key).unwrap(); + assert_eq!(val, read_val, "latest storage value incorrect after generate"); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn dump_and_load_state() { + let sequencer_old = create_test_sequencer().await; + assert_eq!(sequencer_old.block_number().await, 0); + + let declare_tx = create_declare_transaction(ContractAddress(patricia_key!( + sequencer_old.backend.accounts[0].address + ))); + + let tx_hash = declare_tx.inner.transaction_hash(); + + sequencer_old.add_declare_transaction(declare_tx); + + // wait for the tx to be picked up from the mempool, and executed and included in the next block + sleep(Duration::from_millis(500)).await; + + let tx_in_storage = sequencer_old.transaction(&tx_hash.0.into()).await.unwrap(); + + matches!(tx_in_storage, KnownTransaction::Included(_)); + assert_eq!(sequencer_old.block_number().await, 1); + + let serializable_state = sequencer_old + .backend + .state + .read() + .await + .dump_state() + .expect("must be able to serialize state"); + + assert!( + serializable_state.classes.get(&FieldElement::from_hex_be("0x1234").unwrap()).is_some(), + "class must be serialized" + ); + + // instantiate a new sequencer with the serialized state + let (sequencer_config, mut starknet_config) = create_test_sequencer_config(); + starknet_config.init_state = Some(serializable_state); + let sequencer_new = KatanaSequencer::new(sequencer_config, starknet_config).await; + + let old_contract = sequencer_old + .backend + .state + .write() + .await + .get_compiled_contract_class(&ClassHash(stark_felt!("0x1234"))) + .unwrap(); + + let new_contract = sequencer_new + .backend + .state + .write() + .await + .get_compiled_contract_class(&ClassHash(stark_felt!("0x1234"))) + .unwrap(); + + assert_eq!(old_contract, new_contract); +} diff --git a/crates/katana/core/tests/starknet.rs b/crates/katana/core/tests/starknet.rs deleted file mode 100644 index 1cf1098c96..0000000000 --- a/crates/katana/core/tests/starknet.rs +++ /dev/null @@ -1,181 +0,0 @@ -use blockifier::state::state_api::StateReader; -use katana_core::backend::config::{Environment, StarknetConfig}; -use katana_core::backend::storage::transaction::{DeclareTransaction, Transaction}; -use katana_core::backend::Backend; -use katana_core::db::Db; -use katana_core::utils::contract::get_contract_class; -use starknet_api::block::BlockNumber; -use starknet_api::core::{ClassHash, ContractAddress, Nonce, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::state::StorageKey; -use starknet_api::transaction::{ - DeclareTransaction as DeclareApiTransaction, DeclareTransactionV0V1, TransactionHash, -}; -use starknet_api::{patricia_key, stark_felt}; - -fn create_test_starknet_config() -> StarknetConfig { - StarknetConfig { - seed: [0u8; 32], - auto_mine: true, - total_accounts: 2, - disable_fee: true, - env: Environment::default(), - ..Default::default() - } -} - -fn create_test_starknet() -> Backend { - Backend::new(create_test_starknet_config()) -} - -fn create_declare_transaction(sender_address: ContractAddress) -> DeclareTransaction { - let compiled_class = - get_contract_class(include_str!("../contracts/compiled/test_contract.json")); - DeclareTransaction { - inner: DeclareApiTransaction::V0(DeclareTransactionV0V1 { - class_hash: ClassHash(stark_felt!("0x1234")), - nonce: Nonce(1u8.into()), - sender_address, - transaction_hash: TransactionHash(stark_felt!("0x6969")), - ..Default::default() - }), - compiled_class, - sierra_class: None, - } -} - -#[tokio::test] -async fn test_next_block_timestamp_in_past() { - let starknet = create_test_starknet(); - starknet.open_pending_block().await; - - let timestamp = starknet.env.read().block.block_timestamp; - starknet.set_next_block_timestamp(timestamp.0 - 1000).await.unwrap(); - - starknet.open_pending_block().await; - let new_timestamp = starknet.env.read().block.block_timestamp; - - assert_eq!(new_timestamp.0, timestamp.0 - 1000, "timestamp should be updated"); -} - -#[tokio::test] -async fn test_set_next_block_timestamp_in_future() { - let starknet = create_test_starknet(); - starknet.open_pending_block().await; - - let timestamp = starknet.env.read().block.block_timestamp; - starknet.set_next_block_timestamp(timestamp.0 + 1000).await.unwrap(); - - starknet.open_pending_block().await; - let new_timestamp = starknet.env.read().block.block_timestamp; - - assert_eq!(new_timestamp.0, timestamp.0 + 1000, "timestamp should be updated"); -} - -#[tokio::test] -async fn test_increase_next_block_timestamp() { - let starknet = create_test_starknet(); - starknet.open_pending_block().await; - - let timestamp = starknet.env.read().block.block_timestamp; - starknet.increase_next_block_timestamp(1000).await.unwrap(); - - starknet.open_pending_block().await; - let new_timestamp = starknet.env.read().block.block_timestamp; - - assert_eq!(new_timestamp.0, timestamp.0 + 1000, "timestamp should be updated"); -} - -#[tokio::test] -async fn test_creating_blocks() { - let starknet = create_test_starknet(); - starknet.open_pending_block().await; - starknet.mine_block().await; - - assert_eq!(starknet.storage.read().await.blocks.len(), 2); - assert_eq!(starknet.storage.read().await.latest_number, 1); - assert_eq!( - starknet.env.read().block.block_number, - BlockNumber(1), - "block context should only be updated on new pending block" - ); - - let block0 = starknet.storage.read().await.block_by_number(0).unwrap().clone(); - let block1 = starknet.storage.read().await.block_by_number(1).unwrap().clone(); - - assert_eq!(block0.header.number, 0); - assert_eq!(block1.header.number, 1); -} - -#[tokio::test] -async fn dump_and_load_state() { - let backend_old = create_test_starknet(); - backend_old.open_pending_block().await; - - let declare_tx = - create_declare_transaction(ContractAddress(patricia_key!(backend_old.accounts[0].address))); - - backend_old.handle_transaction(Transaction::Declare(declare_tx)).await; - - let serializable_state = - backend_old.state.read().await.dump_state().expect("must be able to serialize state"); - - let mut starknet_config = create_test_starknet_config(); - starknet_config.init_state = Some(serializable_state); - let backend_new = Backend::new(starknet_config); - - let old_contract = backend_old - .state - .write() - .await - .classes - .get(&ClassHash(stark_felt!("0x1234"))) - .cloned() - .unwrap() - .class; - - let new_contract = backend_new - .state - .write() - .await - .classes - .get(&ClassHash(stark_felt!("0x1234"))) - .cloned() - .unwrap() - .class; - - assert_eq!(old_contract, new_contract,); -} - -#[tokio::test] -async fn test_set_storage_at() { - let starknet = create_test_starknet(); - starknet.open_pending_block().await; - - let contract_address = ContractAddress(patricia_key!("0x1337")); - let key = StorageKey(patricia_key!("0x20")); - let val = stark_felt!("0xABC"); - - starknet.set_storage_at(contract_address, key, val).await.unwrap(); - - { - let mut state = starknet.state.write().await; - let read_val = state.get_storage_at(contract_address, key).unwrap(); - assert_eq!(stark_felt!("0x0"), read_val, "latest storage value should be 0"); - } - - { - if let Some(pending_block) = starknet.pending_block.write().await.as_mut() { - let read_val = pending_block.state.get_storage_at(contract_address, key).unwrap(); - assert_eq!(val, read_val, "pending set storage value incorrect"); - } - } - - starknet.mine_block().await; - - { - let mut state = starknet.state.write().await; - let read_val = state.get_storage_at(contract_address, key).unwrap(); - assert_eq!(val, read_val, "latest storage value incorrect after generate"); - } -} diff --git a/crates/katana/rpc/Cargo.toml b/crates/katana/rpc/Cargo.toml index 6f9a2179fe..3ad2feb3d3 100644 --- a/crates/katana/rpc/Cargo.toml +++ b/crates/katana/rpc/Cargo.toml @@ -7,11 +7,12 @@ repository.workspace = true version.workspace = true [dependencies] -anyhow = "1.0.40" +anyhow.workspace = true blockifier.workspace = true -cairo-lang-starknet = "2.1.1" +cairo-lang-starknet = "2.2.0" cairo-vm.workspace = true flate2.workspace = true +futures.workspace = true hex = { version = "0.4.3", default-features = false } hyper = "0.14.20" jsonrpsee = { version = "0.16.2", features = [ "macros", "server" ] } @@ -30,5 +31,4 @@ tracing.workspace = true [dev-dependencies] assert_matches = "1.5.0" dojo-test-utils = { path = "../../dojo-test-utils" } -starknet.workspace = true -url = "2.3.1" +url.workspace = true diff --git a/crates/katana/rpc/src/api/starknet.rs b/crates/katana/rpc/src/api/starknet.rs index 5103b16323..3d387756f7 100644 --- a/crates/katana/rpc/src/api/starknet.rs +++ b/crates/katana/rpc/src/api/starknet.rs @@ -10,13 +10,14 @@ use starknet::core::types::{ ContractClass, DeclareTransactionResult, DeployAccountTransactionResult, EventFilterWithPage, EventsPage, FeeEstimate, FieldElement, FunctionCall, InvokeTransactionResult, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, - StateUpdate, Transaction, + MsgFromL1, StateUpdate, Transaction, }; #[serde_as] #[derive(Serialize, Deserialize)] pub struct Felt(#[serde_as(as = "UfeHex")] pub FieldElement); +// TODO: implement From for StarknetApiError #[derive(thiserror::Error, Clone, Copy, Debug)] pub enum StarknetApiError { #[error("Failed to write transaction")] @@ -45,14 +46,36 @@ pub enum StarknetApiError { ContractError = 40, #[error("Invalid contract class")] InvalidContractClass = 50, + #[error("Class already declared")] + ClassAlreadyDeclared = 51, + #[error("Invalid transaction nonce")] + InvalidTransactionNonce = 52, + #[error("Max fee is smaller than the minimal transaction cost (validation plus fee transfer)")] + InsufficientMaxFee = 53, + #[error("Account balance is smaller than the transaction's max_fee")] + InsufficientAccountBalance = 54, + #[error("Account validation failed")] + ValidationFailure = 55, + #[error("Compilation failed")] + CompilationFailed = 56, + #[error("Contract class size is too large")] + ContractClassSizeIsTooLarge = 57, + #[error("Sender address in not an account contract")] + NonAccount = 58, + #[error("A transaction with the same hash already exists in the mempool")] + DuplicateTransaction = 59, + #[error("The compiled class hash did not match the one supplied in the transaction")] + CompiledClassHashMismatch = 60, + #[error("The transaction version is not supported")] + UnsupportedTransactionVersion = 61, + #[error("The contract class version is not supported")] + UnsupportedContractClassVersion = 62, + #[error("An unexpected error occured")] + UnexpectedError = 63, #[error("Too many storage keys requested")] ProofLimitExceeded = 10000, #[error("Too many keys provided in a filter")] TooManyKeysInFilter = 34, - #[error("Internal server error")] - InternalServerError = 500, - #[error("Unsupported transaction version")] - UnsupportedTransactionVersion = 53, #[error("Failed to fetch pending transactions")] FailedToFetchPendingTransactions = 38, } @@ -65,6 +88,8 @@ impl From for Error { #[rpc(server, namespace = "starknet")] pub trait StarknetApi { + // Read API + #[method(name = "chainId")] async fn chain_id(&self) -> Result; @@ -146,6 +171,13 @@ pub trait StarknetApi { block_id: BlockId, ) -> Result, Error>; + #[method(name = "estimateMessageFee")] + async fn estimate_message_fee( + &self, + message: MsgFromL1, + block_id: BlockId, + ) -> Result; + #[method(name = "call")] async fn call(&self, request: FunctionCall, block_id: BlockId) -> Result, Error>; @@ -157,6 +189,8 @@ pub trait StarknetApi { block_id: BlockId, ) -> Result; + // Write API + #[method(name = "addDeployAccountTransaction")] async fn add_deploy_account_transaction( &self, diff --git a/crates/katana/rpc/src/katana.rs b/crates/katana/rpc/src/katana.rs index 22ed02a1f8..966d9f7384 100644 --- a/crates/katana/rpc/src/katana.rs +++ b/crates/katana/rpc/src/katana.rs @@ -1,6 +1,8 @@ +use std::sync::Arc; + use jsonrpsee::core::{async_trait, Error}; use katana_core::accounts::Account; -use katana_core::sequencer::Sequencer; +use katana_core::sequencer::KatanaSequencer; use starknet::core::types::FieldElement; use starknet_api::core::{ContractAddress, PatriciaKey}; use starknet_api::hash::{StarkFelt, StarkHash}; @@ -9,27 +11,20 @@ use starknet_api::{patricia_key, stark_felt}; use crate::api::katana::{KatanaApiError, KatanaApiServer}; -pub struct KatanaApi { - sequencer: S, +pub struct KatanaApi { + sequencer: Arc, } -impl KatanaApi -where - S: Sequencer + Send + 'static, -{ - pub fn new(sequencer: S) -> Self { +impl KatanaApi { + pub fn new(sequencer: Arc) -> Self { Self { sequencer } } } #[async_trait] -impl KatanaApiServer for KatanaApi -where - S: Sequencer + Send + Sync + 'static, -{ +impl KatanaApiServer for KatanaApi { async fn generate_block(&self) -> Result<(), Error> { - self.sequencer.backend().mine_block().await; - self.sequencer.backend().open_pending_block().await; + self.sequencer.block_producer().force_mine(); Ok(()) } @@ -39,7 +34,6 @@ where async fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), Error> { self.sequencer - .backend() .set_next_block_timestamp(timestamp) .await .map_err(|_| Error::from(KatanaApiError::FailedToChangeNextBlockTimestamp)) @@ -47,7 +41,6 @@ where async fn increase_next_block_timestamp(&self, timestamp: u64) -> Result<(), Error> { self.sequencer - .backend() .increase_next_block_timestamp(timestamp) .await .map_err(|_| Error::from(KatanaApiError::FailedToChangeNextBlockTimestamp)) @@ -64,7 +57,6 @@ where value: FieldElement, ) -> Result<(), Error> { self.sequencer - .backend() .set_storage_at( ContractAddress(patricia_key!(contract_address)), StorageKey(patricia_key!(key)), diff --git a/crates/katana/rpc/src/lib.rs b/crates/katana/rpc/src/lib.rs index f20ffcce5f..1ab9673782 100644 --- a/crates/katana/rpc/src/lib.rs +++ b/crates/katana/rpc/src/lib.rs @@ -15,7 +15,6 @@ use jsonrpsee::server::{AllowHosts, ServerBuilder, ServerHandle}; use jsonrpsee::tracing::debug; use jsonrpsee::types::Params; use jsonrpsee::RpcModule; -use katana_core::sequencer::Sequencer; use tower_http::cors::{Any, CorsLayer}; use crate::api::katana::KatanaApiServer; @@ -23,14 +22,11 @@ use crate::api::starknet::StarknetApiServer; pub use crate::katana::KatanaApi; pub use crate::starknet::StarknetApi; -pub async fn spawn( - katana_api: KatanaApi, - starknet_api: StarknetApi, +pub async fn spawn( + katana_api: KatanaApi, + starknet_api: StarknetApi, config: ServerConfig, -) -> Result -where - S: Sequencer + Send + Sync + 'static, -{ +) -> Result { let mut methods = RpcModule::new(()); methods.merge(starknet_api.into_rpc())?; methods.merge(katana_api.into_rpc())?; @@ -93,7 +89,7 @@ impl Logger for RpcLogger { _kind: MethodKind, _transport: TransportProtocol, ) { - debug!(method = ?method_name); + debug!(target: "server", method = ?method_name); } fn on_result( diff --git a/crates/katana/rpc/src/starknet.rs b/crates/katana/rpc/src/starknet.rs index 5cc8962d89..c0edfb61a8 100644 --- a/crates/katana/rpc/src/starknet.rs +++ b/crates/katana/rpc/src/starknet.rs @@ -1,19 +1,14 @@ use std::sync::Arc; use blockifier::state::errors::StateError; -use blockifier::transaction::account_transaction::AccountTransaction; -use blockifier::transaction::transactions::{ - DeclareTransaction as ExecutionDeclareTransaction, - DeployAccountTransaction as ExecutionDeployAccountTransaction, -}; use jsonrpsee::core::{async_trait, Error}; use katana_core::backend::contract::StarknetContract; use katana_core::backend::storage::transaction::{ DeclareTransaction, DeployAccountTransaction, InvokeTransaction, KnownTransaction, - PendingTransaction, + L1HandlerTransaction, PendingTransaction, Transaction, }; use katana_core::backend::ExternalFunctionCall; -use katana_core::sequencer::Sequencer; +use katana_core::sequencer::KatanaSequencer; use katana_core::sequencer_error::SequencerError; use katana_core::utils::contract::legacy_inner_to_rpc_class; use katana_core::utils::transaction::{ @@ -26,7 +21,7 @@ use starknet::core::types::{ ContractClass, DeclareTransactionResult, DeployAccountTransactionResult, EventFilterWithPage, EventsPage, FeeEstimate, FieldElement, FunctionCall, InvokeTransactionResult, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, - StateUpdate, Transaction, + MsgFromL1, StateUpdate, Transaction as RpcTransaction, }; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, PatriciaKey}; use starknet_api::hash::{StarkFelt, StarkHash}; @@ -36,23 +31,17 @@ use starknet_api::transaction::Calldata; use crate::api::starknet::{Felt, StarknetApiError, StarknetApiServer}; -pub struct StarknetApi { - sequencer: S, +pub struct StarknetApi { + sequencer: Arc, } -impl StarknetApi -where - S: Sequencer + Send + Sync + 'static, -{ - pub fn new(sequencer: S) -> Self { +impl StarknetApi { + pub fn new(sequencer: Arc) -> Self { Self { sequencer } } } #[async_trait] -impl StarknetApiServer for StarknetApi -where - S: Sequencer + Send + Sync + 'static, -{ +impl StarknetApiServer for StarknetApi { async fn chain_id(&self) -> Result { Ok(self.sequencer.chain_id().await.as_hex()) } @@ -67,11 +56,9 @@ where .nonce_at(block_id, ContractAddress(patricia_key!(contract_address))) .await .map_err(|e| match e { - SequencerError::StateNotFound(_) => Error::from(StarknetApiError::BlockNotFound), - SequencerError::ContractNotFound(_) => { - Error::from(StarknetApiError::ContractNotFound) - } - _ => Error::from(StarknetApiError::InternalServerError), + SequencerError::StateNotFound(_) => StarknetApiError::BlockNotFound, + SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, + _ => StarknetApiError::UnexpectedError, })?; Ok(Felt(nonce.0.into())) @@ -84,7 +71,7 @@ where async fn transaction_by_hash( &self, transaction_hash: FieldElement, - ) -> Result { + ) -> Result { let transaction = self .sequencer .transaction(&transaction_hash) @@ -122,12 +109,7 @@ where &self, block_id: BlockId, ) -> Result { - let block = self - .sequencer - .block(block_id) - .await - .ok_or(Error::from(StarknetApiError::BlockNotFound))?; - + let block = self.sequencer.block(block_id).await.ok_or(StarknetApiError::BlockNotFound)?; Ok(block.into()) } @@ -135,29 +117,20 @@ where &self, block_id: BlockId, index: usize, - ) -> Result { - let block = self - .sequencer - .block(block_id) - .await - .ok_or(Error::from(StarknetApiError::BlockNotFound))?; + ) -> Result { + let block = self.sequencer.block(block_id).await.ok_or(StarknetApiError::BlockNotFound)?; let hash: FieldElement = block .transactions() .get(index) .map(|t| t.inner.hash()) - .ok_or(Error::from(StarknetApiError::InvalidTxnIndex))?; + .ok_or(StarknetApiError::InvalidTxnIndex)?; self.transaction_by_hash(hash).await } async fn block_with_txs(&self, block_id: BlockId) -> Result { - let block = self - .sequencer - .block(block_id) - .await - .ok_or(Error::from(StarknetApiError::BlockNotFound))?; - + let block = self.sequencer.block(block_id).await.ok_or(StarknetApiError::BlockNotFound)?; Ok(block.into()) } @@ -165,7 +138,7 @@ where self.sequencer .state_update(block_id) .await - .map_err(|_| Error::from(StarknetApiError::BlockNotFound)) + .map_err(|_| StarknetApiError::BlockNotFound.into()) } async fn transaction_receipt( @@ -175,7 +148,7 @@ where self.sequencer .transaction_receipt(&transaction_hash) .await - .ok_or(Error::from(StarknetApiError::TxnHashNotFound)) + .ok_or(StarknetApiError::TxnHashNotFound.into()) } async fn class_hash_at( @@ -190,7 +163,7 @@ where .map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, - _ => StarknetApiError::InternalServerError, + _ => StarknetApiError::UnexpectedError, })?; Ok(Felt(class_hash.0.into())) @@ -207,14 +180,14 @@ where SequencerError::State(StateError::UndeclaredClassHash(_)) => { StarknetApiError::ClassHashNotFound } - _ => StarknetApiError::InternalServerError, + _ => StarknetApiError::UnexpectedError, }, )?; match contract { StarknetContract::Legacy(c) => { - let contract = legacy_inner_to_rpc_class(c) - .map_err(|_| StarknetApiError::InternalServerError)?; + let contract = + legacy_inner_to_rpc_class(c).map_err(|_| StarknetApiError::UnexpectedError)?; Ok(contract) } StarknetContract::Sierra(c) => Ok(ContractClass::Sierra(c)), @@ -241,13 +214,13 @@ where .await .map_err(|e| match e { SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, - _ => StarknetApiError::InternalServerError, + _ => StarknetApiError::UnexpectedError, })?; Ok(events) } - async fn pending_transactions(&self) -> Result, Error> { + async fn pending_transactions(&self) -> Result, Error> { let block = self.sequencer.block(BlockId::Tag(BlockTag::Pending)).await; Ok(block @@ -255,7 +228,7 @@ where b.transactions() .iter() .map(|tx| KnownTransaction::Pending(PendingTransaction(tx.clone())).into()) - .collect::>() + .collect::>() }) .unwrap_or(Vec::new())) } @@ -270,10 +243,10 @@ where }; let res = self.sequencer.call(block_id, call).await.map_err(|e| match e { - SequencerError::BlockNotFound(_) => Error::from(StarknetApiError::BlockNotFound), - SequencerError::ContractNotFound(_) => Error::from(StarknetApiError::ContractNotFound), - SequencerError::EntryPointExecution(_) => Error::from(StarknetApiError::ContractError), - _ => Error::from(StarknetApiError::InternalServerError), + SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, + SequencerError::ContractNotFound(_) => StarknetApiError::ContractNotFound, + SequencerError::EntryPointExecution(_) => StarknetApiError::ContractError, + _ => StarknetApiError::UnexpectedError, })?; let mut values = vec![]; @@ -300,9 +273,9 @@ where ) .await .map_err(|e| match e { - SequencerError::StateNotFound(_) => Error::from(StarknetApiError::BlockNotFound), - SequencerError::State(_) => Error::from(StarknetApiError::ContractNotFound), - _ => Error::from(StarknetApiError::InternalServerError), + SequencerError::StateNotFound(_) => StarknetApiError::BlockNotFound, + SequencerError::State(_) => StarknetApiError::ContractNotFound, + _ => StarknetApiError::UnexpectedError, })?; Ok(Felt(value.into())) @@ -312,8 +285,12 @@ where &self, deploy_account_transaction: BroadcastedDeployAccountTransaction, ) -> Result { + if deploy_account_transaction.is_query { + return Err(StarknetApiError::UnsupportedTransactionVersion.into()); + } + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) - .map_err(|_| Error::from(StarknetApiError::InternalServerError))?; + .map_err(|_| StarknetApiError::UnexpectedError)?; let (transaction, contract_address) = broadcasted_deploy_account_rpc_to_api_transaction(deploy_account_transaction, chain_id); @@ -335,55 +312,84 @@ where block_id: BlockId, ) -> Result, Error> { let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) - .map_err(|_| Error::from(StarknetApiError::InternalServerError))?; + .map_err(|_| StarknetApiError::UnexpectedError)?; - let mut res = Vec::new(); - - for r in request { - let transaction = match r { + let transactions = request + .into_iter() + .map(|r| match r { BroadcastedTransaction::Declare(tx) => { - let (transaction, contract_class) = + let sierra_class = match tx { + BroadcastedDeclareTransaction::V2(ref tx) => { + Some(tx.contract_class.as_ref().clone()) + } + _ => None, + }; + + let (transaction, compiled_class) = broadcasted_declare_rpc_to_api_transaction(tx, chain_id).unwrap(); - AccountTransaction::Declare( - ExecutionDeclareTransaction::new(transaction, contract_class) - .map_err(|_| Error::from(StarknetApiError::InternalServerError))?, - ) + Transaction::Declare(DeclareTransaction { + sierra_class, + compiled_class, + inner: transaction, + }) } BroadcastedTransaction::Invoke(tx) => { let transaction = broadcasted_invoke_rpc_to_api_transaction(tx, chain_id); - AccountTransaction::Invoke(transaction) + Transaction::Invoke(InvokeTransaction(transaction)) } BroadcastedTransaction::DeployAccount(tx) => { let (transaction, contract_address) = broadcasted_deploy_account_rpc_to_api_transaction(tx, chain_id); - AccountTransaction::DeployAccount(ExecutionDeployAccountTransaction { - tx: transaction, - contract_address: ContractAddress(patricia_key!(contract_address)), + Transaction::DeployAccount(DeployAccountTransaction { + contract_address, + inner: transaction, }) } - }; - - let fee_estimate = - self.sequencer.estimate_fee(transaction, block_id).await.map_err(|e| match e { - SequencerError::BlockNotFound(_) => { - Error::from(StarknetApiError::BlockNotFound) - } - SequencerError::TransactionExecution(_) => { - Error::from(StarknetApiError::ContractError) - } - _ => Error::from(StarknetApiError::InternalServerError), - })?; - - res.push(FeeEstimate { - gas_price: fee_estimate.gas_price, - overall_fee: fee_estimate.overall_fee, - gas_consumed: fee_estimate.gas_consumed, - }); - } + }) + .collect::>(); + + let res = + self.sequencer.estimate_fee(transactions, block_id).await.map_err(|e| match e { + SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, + SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, + _ => StarknetApiError::UnexpectedError, + })?; + + Ok(res) + } + + async fn estimate_message_fee( + &self, + message: MsgFromL1, + block_id: BlockId, + ) -> Result { + let l1handler_tx = L1HandlerTransaction { + inner: starknet_api::transaction::L1HandlerTransaction { + contract_address: ContractAddress(patricia_key!(message.to_address)), + calldata: Calldata(Arc::new( + message.payload.into_iter().map(|f| f.into()).collect(), + )), + entry_point_selector: EntryPointSelector(message.entry_point_selector.into()), + ..Default::default() + }, + paid_l1_fee: 1, + }; + + let res = self + .sequencer + .estimate_fee(vec![Transaction::L1Handler(l1handler_tx)], block_id) + .await + .map_err(|e| match e { + SequencerError::BlockNotFound(_) => StarknetApiError::BlockNotFound, + SequencerError::TransactionExecution(_) => StarknetApiError::ContractError, + _ => StarknetApiError::UnexpectedError, + })? + .pop() + .expect("should have estimate result"); Ok(res) } @@ -392,8 +398,15 @@ where &self, declare_transaction: BroadcastedDeclareTransaction, ) -> Result { + if match &declare_transaction { + BroadcastedDeclareTransaction::V1(tx) => tx.is_query, + BroadcastedDeclareTransaction::V2(tx) => tx.is_query, + } { + return Err(StarknetApiError::UnsupportedTransactionVersion.into()); + } + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) - .map_err(|_| Error::from(StarknetApiError::InternalServerError))?; + .map_err(|_| StarknetApiError::UnexpectedError)?; let sierra_class = match declare_transaction { BroadcastedDeclareTransaction::V2(ref tx) => Some(tx.contract_class.as_ref().clone()), @@ -406,13 +419,11 @@ where let transaction_hash = transaction.transaction_hash().0.into(); let class_hash = transaction.class_hash().0.into(); - self.sequencer - .add_declare_transaction(DeclareTransaction { - sierra_class, - inner: transaction, - compiled_class: contract_class, - }) - .await; + self.sequencer.add_declare_transaction(DeclareTransaction { + sierra_class, + inner: transaction, + compiled_class: contract_class, + }); Ok(DeclareTransactionResult { transaction_hash, class_hash }) } @@ -421,13 +432,17 @@ where &self, invoke_transaction: BroadcastedInvokeTransaction, ) -> Result { + if invoke_transaction.is_query { + return Err(StarknetApiError::UnsupportedTransactionVersion.into()); + } + let chain_id = FieldElement::from_hex_be(&self.sequencer.chain_id().await.as_hex()) - .map_err(|_| Error::from(StarknetApiError::InternalServerError))?; + .map_err(|_| StarknetApiError::UnexpectedError)?; let transaction = broadcasted_invoke_rpc_to_api_transaction(invoke_transaction, chain_id); let transaction_hash = transaction.transaction_hash().0.into(); - self.sequencer.add_invoke_transaction(InvokeTransaction(transaction)).await; + self.sequencer.add_invoke_transaction(InvokeTransaction(transaction)); Ok(InvokeTransactionResult { transaction_hash }) } diff --git a/crates/katana/rpc/tests/starknet.rs b/crates/katana/rpc/tests/starknet.rs index 7894cb9c83..a0ece05aa2 100644 --- a/crates/katana/rpc/tests/starknet.rs +++ b/crates/katana/rpc/tests/starknet.rs @@ -1,6 +1,7 @@ use std::fs::{self, File}; use std::path::PathBuf; use std::sync::Arc; +use std::time::Duration; use anyhow::{anyhow, Result}; use cairo_lang_starknet::casm_contract_class::CasmContractClass; @@ -12,12 +13,12 @@ use starknet::core::types::contract::legacy::LegacyContractClass; use starknet::core::types::contract::{CompiledClass, SierraClass}; use starknet::core::types::{ BlockId, BlockTag, DeclareTransactionReceipt, FieldElement, FlattenedSierraClass, - MaybePendingTransactionReceipt, TransactionReceipt, TransactionStatus, + MaybePendingTransactionReceipt, TransactionFinalityStatus, TransactionReceipt, }; use starknet::core::utils::{get_contract_address, get_selector_from_name}; use starknet::providers::Provider; -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_send_declare_and_deploy_contract() { let sequencer = TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; @@ -28,13 +29,17 @@ async fn test_send_declare_and_deploy_contract() { let class_hash = contract.class_hash(); let res = account.declare(Arc::new(contract), compiled_class_hash).send().await.unwrap(); + + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; + let receipt = account.provider().get_transaction_receipt(res.transaction_hash).await.unwrap(); match receipt { MaybePendingTransactionReceipt::Receipt(TransactionReceipt::Declare( - DeclareTransactionReceipt { status, .. }, + DeclareTransactionReceipt { finality_status, .. }, )) => { - assert_eq!(status, TransactionStatus::AcceptedOnL2); + assert_eq!(finality_status, TransactionFinalityStatus::AcceptedOnL2); } _ => panic!("invalid tx receipt"), } @@ -75,6 +80,9 @@ async fn test_send_declare_and_deploy_contract() { .await .unwrap(); + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; + assert_eq!( account .provider() @@ -87,8 +95,8 @@ async fn test_send_declare_and_deploy_contract() { sequencer.stop().expect("failed to stop sequencer"); } -#[tokio::test] -async fn test_send_declare_and_deploy_legcay_contract() { +#[tokio::test(flavor = "multi_thread")] +async fn test_send_declare_and_deploy_legacy_contract() { let sequencer = TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; let account = sequencer.account(); @@ -101,13 +109,16 @@ async fn test_send_declare_and_deploy_legcay_contract() { let class_hash = contract_class.class_hash().unwrap(); let res = account.declare_legacy(contract_class).send().await.unwrap(); + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; + let receipt = account.provider().get_transaction_receipt(res.transaction_hash).await.unwrap(); match receipt { MaybePendingTransactionReceipt::Receipt(TransactionReceipt::Declare( - DeclareTransactionReceipt { status, .. }, + DeclareTransactionReceipt { finality_status, .. }, )) => { - assert_eq!(status, TransactionStatus::AcceptedOnL2); + assert_eq!(finality_status, TransactionFinalityStatus::AcceptedOnL2); } _ => panic!("invalid tx receipt"), } @@ -148,6 +159,9 @@ async fn test_send_declare_and_deploy_legcay_contract() { .await .unwrap(); + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; + assert_eq!( account .provider() diff --git a/crates/katana/src/args.rs b/crates/katana/src/args.rs index 2f5dd9554f..88f0885a99 100644 --- a/crates/katana/src/args.rs +++ b/crates/katana/src/args.rs @@ -9,6 +9,7 @@ use katana_core::constants::{ use katana_core::db::serde::state::SerializableState; use katana_core::sequencer::SequencerConfig; use katana_rpc::config::ServerConfig; +use url::Url; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -20,12 +21,12 @@ pub struct KatanaArgs { #[arg(long)] #[arg(conflicts_with = "block_time")] - #[arg(help = "Disable auto and interval mining, and mine on demand instead.")] + #[arg(help = "Disable auto and interval mining, and mine on demand instead via an endpoint.")] pub no_mining: bool, #[arg(short, long)] - #[arg(value_name = "SECONDS")] - #[arg(help = "Block time in seconds for interval mining.")] + #[arg(value_name = "MILLISECONDS")] + #[arg(help = "Block time in milliseconds for interval mining.")] pub block_time: Option, #[arg(long)] @@ -35,6 +36,17 @@ pub struct KatanaArgs { directory, the state will be written to `/state.bin`.")] pub dump_state: Option, + #[arg(long)] + #[arg(value_name = "URL")] + #[arg(help = "The Starknet RPC provider to fork the network from.")] + pub rpc_url: Option, + + #[arg(long)] + #[arg(requires = "rpc_url")] + #[arg(value_name = "BLOCK_NUMBER")] + #[arg(help = "Fork the network at a specific block.")] + pub fork_block_number: Option, + #[arg(long)] #[arg(value_name = "PATH")] #[arg(value_parser = SerializableState::parse)] @@ -115,7 +127,7 @@ pub struct EnvironmentOptions { impl KatanaArgs { pub fn sequencer_config(&self) -> SequencerConfig { - SequencerConfig { block_time: self.block_time } + SequencerConfig { block_time: self.block_time, no_mining: self.no_mining } } pub fn server_config(&self) -> ServerConfig { @@ -130,8 +142,9 @@ impl KatanaArgs { total_accounts: self.starknet.total_accounts, seed: parse_seed(&self.starknet.seed), disable_fee: self.starknet.disable_fee, - auto_mine: self.block_time.is_none() && !self.no_mining, init_state: self.load_state.clone(), + fork_rpc_url: self.rpc_url.clone(), + fork_block_number: self.fork_block_number, env: Environment { chain_id: self.starknet.environment.chain_id.clone(), gas_price: self.starknet.environment.gas_price.unwrap_or(DEFAULT_GAS_PRICE), diff --git a/crates/katana/src/main.rs b/crates/katana/src/main.rs index d4c8845f01..ec6c9ce81e 100644 --- a/crates/katana/src/main.rs +++ b/crates/katana/src/main.rs @@ -5,11 +5,11 @@ use std::{fs, io}; use clap::{CommandFactory, Parser}; use clap_complete::{generate, Shell}; use console::Style; -use env_logger::Env; -use katana_core::sequencer::{KatanaSequencer, Sequencer}; +use katana_core::sequencer::KatanaSequencer; use katana_rpc::{spawn, KatanaApi, NodeHandle, StarknetApi}; -use log::{error, info}; use tokio::signal::ctrl_c; +use tracing::{error, info}; +use tracing_subscriber::fmt; mod args; @@ -18,10 +18,15 @@ use args::KatanaArgs; #[tokio::main] async fn main() { - env_logger::Builder::from_env(Env::default().default_filter_or( - "info,katana_rpc=debug,katana_core=trace,blockifier=off,jsonrpsee_server=off,hyper=off", - )) - .init(); + tracing::subscriber::set_global_default( + fmt::Subscriber::builder() + .with_env_filter( + "info,executor=trace,server=debug,katana_core=trace,blockifier=off,\ + jsonrpsee_server=off,hyper=off", + ) + .finish(), + ) + .expect("Failed to set the global tracing subscriber"); let config = KatanaArgs::parse(); if let Some(command) = config.command { @@ -37,7 +42,7 @@ async fn main() { let sequencer_config = config.sequencer_config(); let starknet_config = config.starknet_config(); - let sequencer = Arc::new(KatanaSequencer::new(sequencer_config, starknet_config)); + let sequencer = Arc::new(KatanaSequencer::new(sequencer_config, starknet_config).await); let starknet_api = StarknetApi::new(sequencer.clone()); let katana_api = KatanaApi::new(sequencer.clone()); @@ -62,15 +67,13 @@ async fn main() { ); } - sequencer.start().await; - // Wait until Ctrl + C is pressed, then shutdown ctrl_c().await.unwrap(); shutdown_handler(sequencer.clone(), config).await; handle.stop().unwrap(); } Err(err) => { - error! {"{}", err}; + error! {"{err}"}; exit(1); } }; @@ -119,7 +122,7 @@ ACCOUNTS SEED println!("\n{address}\n\n"); } -pub async fn shutdown_handler(sequencer: Arc, config: KatanaArgs) { +pub async fn shutdown_handler(sequencer: Arc, config: KatanaArgs) { if let Some(path) = config.dump_state { info!("Dumping state on shutdown"); let state = (*sequencer).backend().dump_state().await; diff --git a/crates/sozo/Cargo.toml b/crates/sozo/Cargo.toml index 17412c1ebc..460666788f 100644 --- a/crates/sozo/Cargo.toml +++ b/crates/sozo/Cargo.toml @@ -9,6 +9,7 @@ version = "0.2.1" anyhow.workspace = true async-trait.workspace = true cairo-lang-compiler.workspace = true +cairo-lang-defs.workspace = true cairo-lang-filesystem.workspace = true cairo-lang-plugins.workspace = true cairo-lang-project.workspace = true @@ -22,8 +23,11 @@ clap.workspace = true clap_complete.workspace = true console.workspace = true dojo-lang = { path = "../dojo-lang" } +dojo-types = { path = "../dojo-types" } dojo-world = { path = "../dojo-world" } -log.workspace = true +notify = "6.0.1" +notify-debouncer-mini = "0.3.0" +scarb-ui.workspace = true scarb.workspace = true semver.workspace = true serde.workspace = true @@ -31,18 +35,13 @@ serde_json.workspace = true smol_str.workspace = true starknet.workspace = true thiserror.workspace = true -tokio = { version = "1.15.0", features = [ "full" ] } +tokio.workspace = true torii-client = { path = "../torii/client" } tracing-log = "0.1.3" tracing.workspace = true -url = "2.2.2" +url.workspace = true [dev-dependencies] assert_fs = "1.0.10" dojo-test-utils = { path = "../dojo-test-utils", features = [ "build-examples" ] } snapbox = "0.4.6" -tokio = { version = "1.28.0", features = [ "full" ] } - -[[bin]] -name = "sozo" -path = "src/main.rs" diff --git a/crates/sozo/src/args.rs b/crates/sozo/src/args.rs index a5c88af2d2..909c7ba10d 100644 --- a/crates/sozo/src/args.rs +++ b/crates/sozo/src/args.rs @@ -2,7 +2,7 @@ use anyhow::Result; use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; use scarb::compiler::Profile; -use scarb::ui; +use scarb_ui::Verbosity; use smol_str::SmolStr; use tracing::level_filters::LevelFilter; use tracing_log::AsTrace; @@ -10,13 +10,13 @@ use tracing_log::AsTrace; use crate::commands::auth::AuthArgs; use crate::commands::build::BuildArgs; use crate::commands::completions::CompletionsArgs; -use crate::commands::component::ComponentArgs; +use crate::commands::dev::DevArgs; use crate::commands::events::EventsArgs; use crate::commands::execute::ExecuteArgs; use crate::commands::init::InitArgs; use crate::commands::migrate::MigrateArgs; +use crate::commands::model::ModelArgs; use crate::commands::register::RegisterArgs; -use crate::commands::system::SystemArgs; use crate::commands::test::TestArgs; #[derive(Parser)] @@ -57,15 +57,15 @@ pub enum Commands { #[command(about = "Run a migration, declaring and deploying contracts as necessary to \ update the world")] Migrate(Box), + #[command(about = "Developer mode: watcher for building and migration")] + Dev(DevArgs), #[command(about = "Test the project's smart contracts")] Test(TestArgs), #[command(about = "Execute a world's system")] Execute(ExecuteArgs), - #[command(about = "Interact with a worlds components")] - Component(ComponentArgs), - #[command(about = "Interact with a worlds systems")] - System(SystemArgs), - #[command(about = "Register new systems and components")] + #[command(about = "Interact with a worlds models")] + Model(ModelArgs), + #[command(about = "Register new models")] Register(RegisterArgs), #[command(about = "Queries world events")] Events(EventsArgs), @@ -76,14 +76,14 @@ pub enum Commands { } impl SozoArgs { - pub fn ui_verbosity(&self) -> ui::Verbosity { + pub fn ui_verbosity(&self) -> Verbosity { let filter = self.verbose.log_level_filter().as_trace(); if filter >= LevelFilter::WARN { - ui::Verbosity::Verbose + Verbosity::Verbose } else if filter > LevelFilter::OFF { - ui::Verbosity::Normal + Verbosity::Normal } else { - ui::Verbosity::Quiet + Verbosity::Quiet } } } diff --git a/crates/sozo/src/commands/auth.rs b/crates/sozo/src/commands/auth.rs index 51366b3921..c004e386cb 100644 --- a/crates/sozo/src/commands/auth.rs +++ b/crates/sozo/src/commands/auth.rs @@ -18,8 +18,8 @@ pub struct AuthArgs { pub enum AuthCommand { #[command(about = "Auth a system with the given calldata.")] Writer { - #[arg(help = "Name of the component to grant write access to.")] - component: String, + #[arg(help = "Name of the model to grant write access to.")] + model: String, #[arg(help = "Name of the system to grant writer access to.")] system: String, diff --git a/crates/sozo/src/commands/completions.rs b/crates/sozo/src/commands/completions.rs index 123cb04659..65c99ac2bb 100644 --- a/crates/sozo/src/commands/completions.rs +++ b/crates/sozo/src/commands/completions.rs @@ -4,7 +4,7 @@ use anyhow::Result; use clap::{Args, CommandFactory}; use clap_complete::{generate, Shell}; -use crate::SozoArgs; +use crate::args::SozoArgs; #[derive(Args, Debug)] pub struct CompletionsArgs { diff --git a/crates/sozo/src/commands/dev.rs b/crates/sozo/src/commands/dev.rs new file mode 100644 index 0000000000..788a0c9132 --- /dev/null +++ b/crates/sozo/src/commands/dev.rs @@ -0,0 +1,273 @@ +use std::mem; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::channel; +use std::time::Duration; + +use anyhow::{anyhow, Result}; +use cairo_lang_compiler::db::RootDatabase; +use cairo_lang_filesystem::db::{AsFilesGroupMut, FilesGroupEx, PrivRawFileContentQuery}; +use cairo_lang_filesystem::ids::FileId; +use clap::Args; +use dojo_world::manifest::Manifest; +use dojo_world::metadata::{dojo_metadata_from_workspace, DojoMetadata}; +use dojo_world::migration::world::WorldDiff; +use notify_debouncer_mini::notify::RecursiveMode; +use notify_debouncer_mini::{new_debouncer, DebouncedEvent, DebouncedEventKind}; +use scarb::compiler::CompilationUnit; +use scarb::core::{Config, Workspace}; +use starknet::accounts::SingleOwnerAccount; +use starknet::core::types::FieldElement; +use starknet::providers::Provider; +use starknet::signers::Signer; +use tracing_log::log; + +use super::options::account::AccountOptions; +use super::options::starknet::StarknetOptions; +use super::options::world::WorldOptions; +use super::scarb_internal::build_scarb_root_database; +use crate::ops::migration; + +#[derive(Args)] +pub struct DevArgs { + #[arg(long)] + #[arg(help = "Name of the World.")] + #[arg(long_help = "Name of the World. It's hash will be used as a salt when deploying the \ + contract to avoid address conflicts.")] + pub name: Option, + + #[command(flatten)] + pub world: WorldOptions, + + #[command(flatten)] + pub starknet: StarknetOptions, + + #[command(flatten)] + pub account: AccountOptions, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +enum DevAction { + None, + Reload, + Build(PathBuf), +} + +fn handle_event(event: &DebouncedEvent) -> DevAction { + let action = match event.kind { + DebouncedEventKind::Any => { + let p = event.path.clone(); + if let Some(filename) = p.file_name() { + if filename == "Scarb.toml" { + return DevAction::Reload; + } else if let Some(extension) = p.extension() { + if extension == "cairo" { + return DevAction::Build(p.clone()); + } + } + } + DevAction::None + } + _ => DevAction::None, + }; + action +} + +struct DevContext<'a> { + pub db: RootDatabase, + pub unit: CompilationUnit, + pub ws: Workspace<'a>, + pub dojo_metadata: Option, +} + +fn load_context(config: &Config) -> Result> { + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; + let packages: Vec = ws.members().map(|p| p.id).collect(); + let resolve = scarb::ops::resolve_workspace(&ws)?; + let compilation_units = scarb::ops::generate_compilation_units(&resolve, &ws)? + .into_iter() + .filter(|cu| packages.contains(&cu.main_package_id)) + .collect::>(); + // we have only 1 unit in projects + let unit = compilation_units.get(0).unwrap(); + let db = build_scarb_root_database(unit, &ws).unwrap(); + let dojo_metadata = dojo_metadata_from_workspace(&ws); + Ok(DevContext { db, unit: unit.clone(), ws, dojo_metadata }) +} + +fn build(context: &mut DevContext<'_>) -> Result<()> { + let ws = &context.ws; + let unit = &context.unit; + let package_name = unit.main_package_id.name.clone(); + ws.config().compilers().compile(unit.clone(), &mut (context.db), ws).map_err(|err| { + ws.config().ui().anyhow(&err); + + anyhow!("could not compile `{package_name}` due to previous error") + })?; + ws.config().ui().print("📦 Rebuild done"); + Ok(()) +} + +async fn migrate( + mut world_address: Option, + account: &SingleOwnerAccount, + name: Option, + ws: &Workspace<'_>, + previous_manifest: Option, +) -> Result<(Manifest, Option)> +where + P: Provider + Sync + Send + 'static, + S: Signer + Sync + Send + 'static, +{ + let target_dir = ws.target_dir().path_existent().unwrap(); + let target_dir = target_dir.join(ws.config().profile().as_str()); + let manifest_path = target_dir.join("manifest.json"); + if !manifest_path.exists() { + return Err(anyhow!("manifest.json not found")); + } + let new_manifest = Manifest::load_from_path(manifest_path)?; + let diff = WorldDiff::compute(new_manifest.clone(), previous_manifest); + let total_diffs = diff.count_diffs(); + let config = ws.config(); + config.ui().print(format!("Total diffs found: {total_diffs}")); + if total_diffs == 0 { + return Ok((new_manifest, world_address)); + } + match migration::apply_diff( + target_dir, + diff, + name.clone(), + world_address, + account, + config, + None, + ) + .await + { + Ok(address) => { + config + .ui() + .print(format!("🎉 World at address {} updated!", format_args!("{:#x}", address))); + world_address = Some(address); + } + Err(err) => { + config.ui().error(err.to_string()); + return Err(err); + } + } + + Ok((new_manifest, world_address)) +} + +fn process_event(event: &DebouncedEvent, context: &mut DevContext<'_>) -> DevAction { + let action = handle_event(event); + match &action { + DevAction::None => {} + DevAction::Build(path) => handle_build_action(path, context), + DevAction::Reload => { + handle_reload_action(context); + } + } + action +} + +fn handle_build_action(path: &Path, context: &mut DevContext<'_>) { + context + .ws + .config() + .ui() + .print(format!("📦 Need to rebuild {}", path.to_str().unwrap_or_default(),)); + let db = &mut context.db; + let file = FileId::new(db, path.to_path_buf()); + PrivRawFileContentQuery.in_db_mut(db.as_files_group_mut()).invalidate(&file); + db.override_file_content(file, None); +} + +fn handle_reload_action(context: &mut DevContext<'_>) { + let config = context.ws.config(); + config.ui().print("Reloading project"); + let new_context = load_context(config).expect("Failed to load context"); + let _ = mem::replace(context, new_context); +} + +impl DevArgs { + pub fn run(self, config: &Config) -> Result<()> { + let mut context = load_context(config)?; + let (tx, rx) = channel(); + let mut debouncer = new_debouncer(Duration::from_secs(1), None, tx)?; + + debouncer.watcher().watch( + config.manifest_path().parent().unwrap().as_std_path(), + RecursiveMode::Recursive, + )?; + let name = self.name.clone(); + let mut previous_manifest: Option = Option::None; + let result = build(&mut context); + let env_metadata = context.dojo_metadata.as_ref().and_then(|e| e.env.clone()); + + let Some((mut world_address, account)) = context + .ws + .config() + .tokio_handle() + .block_on(migration::setup_env( + self.account, + self.starknet, + self.world, + env_metadata.as_ref(), + config, + name.as_ref(), + )) + .ok() + else { + return Err(anyhow!("Failed to setup environment")); + }; + + match context.ws.config().tokio_handle().block_on(migrate( + world_address, + &account, + name.clone(), + &context.ws, + previous_manifest.clone(), + )) { + Ok((manifest, address)) => { + previous_manifest = Some(manifest); + world_address = address; + } + Err(error) => { + log::error!("Error: {error:?}"); + } + } + loop { + let action = match rx.recv() { + Ok(Ok(events)) => events + .iter() + .map(|event| process_event(event, &mut context)) + .last() + .unwrap_or(DevAction::None), + Ok(Err(_)) => DevAction::None, + Err(error) => { + log::error!("Error: {error:?}"); + break; + } + }; + + if action != DevAction::None && build(&mut context).is_ok() { + match context.ws.config().tokio_handle().block_on(migrate( + world_address, + &account, + name.clone(), + &context.ws, + previous_manifest.clone(), + )) { + Ok((manifest, address)) => { + previous_manifest = Some(manifest); + world_address = address; + } + Err(error) => { + log::error!("Error: {error:?}"); + } + } + } + } + result + } +} diff --git a/crates/sozo/src/commands/events.rs b/crates/sozo/src/commands/events.rs index d97435d772..eba63efc25 100644 --- a/crates/sozo/src/commands/events.rs +++ b/crates/sozo/src/commands/events.rs @@ -1,7 +1,12 @@ -use anyhow::Result; +use std::collections::HashMap; + +use anyhow::{anyhow, Result}; +use cairo_lang_starknet::abi::{self, Event, Item}; use clap::Parser; +use dojo_world::manifest::Manifest; use dojo_world::metadata::dojo_metadata_from_workspace; use scarb::core::Config; +use starknet::core::utils::starknet_keccak; use super::options::starknet::StarknetOptions; use super::options::world::WorldOptions; @@ -29,6 +34,10 @@ pub struct EventsArgs { #[arg(help = "Continuation string to be passed for rpc request")] pub continuation_token: Option, + #[arg(long)] + #[arg(help = "Print values as raw json")] + pub json: bool, + #[command(flatten)] pub world: WorldOptions, @@ -38,6 +47,15 @@ pub struct EventsArgs { impl EventsArgs { pub fn run(self, config: &Config) -> Result<()> { + let target_dir = config.target_dir().path_existent().unwrap(); + let manifest_path = target_dir.join(config.profile().as_str()).join("manifest.json"); + + if !manifest_path.exists() { + return Err(anyhow!("Run scarb migrate before running this command")); + } + + let manifest = Manifest::load_from_path(manifest_path)?; + let events = extract_events(&manifest); let env_metadata = if config.manifest_path().exists() { let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; @@ -46,10 +64,49 @@ impl EventsArgs { } else { None }; - config.tokio_handle().block_on(events::execute(self, env_metadata)) + config.tokio_handle().block_on(events::execute(self, env_metadata, events)) } } +fn extract_events(manifest: &Manifest) -> HashMap> { + fn inner_helper(events: &mut HashMap>, contract: &Option) { + if let Some(contract) = contract { + for item in &contract.items { + if let Item::Event(e) = item { + match e.kind { + abi::EventKind::Struct { .. } => { + let event_name = + starknet_keccak(e.name.split("::").last().unwrap().as_bytes()); + let vec = events.entry(event_name.to_string()).or_insert(Vec::new()); + vec.push(e.clone()); + } + abi::EventKind::Enum { .. } => (), + } + } + } + } + } + + let mut events_map = HashMap::new(); + + inner_helper(&mut events_map, &manifest.world.abi); + inner_helper(&mut events_map, &manifest.executor.abi); + + for system in &manifest.systems { + inner_helper(&mut events_map, &system.abi); + } + + for contract in &manifest.contracts { + inner_helper(&mut events_map, &contract.abi); + } + + for model in &manifest.models { + inner_helper(&mut events_map, &model.abi); + } + + events_map +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/sozo/src/commands/execute.rs b/crates/sozo/src/commands/execute.rs index d0a463bb15..0e11a2077c 100644 --- a/crates/sozo/src/commands/execute.rs +++ b/crates/sozo/src/commands/execute.rs @@ -6,14 +6,16 @@ use starknet::core::types::FieldElement; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; -use super::options::world::WorldOptions; use crate::ops::execute; #[derive(Debug, Args)] #[command(about = "Execute a system with the given calldata.")] pub struct ExecuteArgs { - #[arg(help = "The name of the system to be executed.")] - pub system: String, + #[arg(help = "The address of the contract to be executed.")] + pub contract: FieldElement, + + #[arg(help = "The name of the entrypoint to be executed.")] + pub entrypoint: String, #[arg(short, long)] #[arg(value_delimiter = ',')] @@ -21,9 +23,6 @@ pub struct ExecuteArgs { 0x12345,0x69420.")] pub calldata: Vec, - #[command(flatten)] - pub world: WorldOptions, - #[command(flatten)] pub starknet: StarknetOptions, diff --git a/crates/sozo/src/commands/migrate.rs b/crates/sozo/src/commands/migrate.rs index b43f64b500..10d4236d94 100644 --- a/crates/sozo/src/commands/migrate.rs +++ b/crates/sozo/src/commands/migrate.rs @@ -5,6 +5,7 @@ use scarb::core::Config; use super::options::account::AccountOptions; use super::options::starknet::StarknetOptions; +use super::options::transaction::TransactionOptions; use super::options::world::WorldOptions; use crate::ops::migration; @@ -28,6 +29,9 @@ pub struct MigrateArgs { #[command(flatten)] pub account: AccountOptions, + + #[command(flatten)] + pub transaction: TransactionOptions, } impl MigrateArgs { diff --git a/crates/sozo/src/commands/mod.rs b/crates/sozo/src/commands/mod.rs index c4e9b7c4f5..e093ab8ab8 100644 --- a/crates/sozo/src/commands/mod.rs +++ b/crates/sozo/src/commands/mod.rs @@ -6,27 +6,29 @@ use crate::args::Commands; pub(crate) mod auth; pub(crate) mod build; pub(crate) mod completions; -pub(crate) mod component; +pub(crate) mod dev; pub(crate) mod events; pub(crate) mod execute; pub(crate) mod init; pub(crate) mod migrate; +pub(crate) mod model; pub(crate) mod options; pub(crate) mod register; -pub(crate) mod system; pub(crate) mod test; +// copy of non pub functions from scarb +pub(crate) mod scarb_internal; + pub fn run(command: Commands, config: &Config) -> Result<()> { match command { Commands::Init(args) => args.run(config), Commands::Test(args) => args.run(config), Commands::Build(args) => args.run(config), Commands::Migrate(args) => args.run(config), - + Commands::Dev(args) => args.run(config), Commands::Auth(args) => args.run(config), Commands::Execute(args) => args.run(config), - Commands::Component(args) => args.run(config), - Commands::System(args) => args.run(config), + Commands::Model(args) => args.run(config), Commands::Register(args) => args.run(config), Commands::Events(args) => args.run(config), Commands::Completions(args) => args.run(), diff --git a/crates/sozo/src/commands/component.rs b/crates/sozo/src/commands/model.rs similarity index 73% rename from crates/sozo/src/commands/component.rs rename to crates/sozo/src/commands/model.rs index 67046a817d..a460b00679 100644 --- a/crates/sozo/src/commands/component.rs +++ b/crates/sozo/src/commands/model.rs @@ -6,19 +6,19 @@ use starknet::core::types::FieldElement; use super::options::starknet::StarknetOptions; use super::options::world::WorldOptions; -use crate::ops::component; +use crate::ops::model; #[derive(Debug, Args)] -pub struct ComponentArgs { +pub struct ModelArgs { #[command(subcommand)] - command: ComponentCommands, + command: ModelCommands, } #[derive(Debug, Subcommand)] -pub enum ComponentCommands { - #[command(about = "Get the class hash of a component")] - Get { - #[arg(help = "The name of the component")] +pub enum ModelCommands { + #[command(about = "Retrieve the class hash of a model")] + ClassHash { + #[arg(help = "The name of the model")] name: String, #[command(flatten)] @@ -28,9 +28,9 @@ pub enum ComponentCommands { starknet: StarknetOptions, }, - #[command(about = "Retrieve the schema for a component")] + #[command(about = "Retrieve the schema for a model")] Schema { - #[arg(help = "The name of the component")] + #[arg(help = "The name of the model")] name: String, #[command(flatten)] @@ -44,9 +44,9 @@ pub enum ComponentCommands { to_json: bool, }, - #[command(about = "Get the component value for an entity")] + #[command(about = "Retrieve the model value for an entity")] Entity { - #[arg(help = "The name of the component")] + #[arg(help = "The name of the model")] name: String, #[arg(value_name = "KEYS")] @@ -62,7 +62,7 @@ pub enum ComponentCommands { }, } -impl ComponentArgs { +impl ModelArgs { pub fn run(self, config: &Config) -> Result<()> { let env_metadata = if config.manifest_path().exists() { let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; @@ -73,6 +73,6 @@ impl ComponentArgs { None }; - config.tokio_handle().block_on(component::execute(self.command, env_metadata)) + config.tokio_handle().block_on(model::execute(self.command, env_metadata)) } } diff --git a/crates/sozo/src/commands/options/account.rs b/crates/sozo/src/commands/options/account.rs index 97e4ceb018..9936eebe81 100644 --- a/crates/sozo/src/commands/options/account.rs +++ b/crates/sozo/src/commands/options/account.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use anyhow::{anyhow, Context, Result}; use clap::Args; use dojo_world::metadata::Environment; -use starknet::accounts::SingleOwnerAccount; +use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; use starknet::core::types::FieldElement; use starknet::providers::Provider; use starknet::signers::{LocalWallet, SigningKey}; @@ -50,7 +50,17 @@ impl AccountOptions { let chain_id = provider.chain_id().await.with_context(|| "Failed to retrieve network chain id.")?; - Ok(SingleOwnerAccount::new(provider, signer, account_address, chain_id)) + Ok(SingleOwnerAccount::new( + provider, + signer, + account_address, + chain_id, + // This is made under the assumption that the accounts used with `sozo` commands would + // be one of the `katana` dev accounts. The dev accounts deployed on `katana` are + // legacy accounts (Cairo 0). + // TODO: Make this configurable + ExecutionEncoding::Legacy, + )) } fn signer(&self, env_metadata: Option<&Environment>) -> Result { diff --git a/crates/sozo/src/commands/options/mod.rs b/crates/sozo/src/commands/options/mod.rs index 21f968db17..e7d2645387 100644 --- a/crates/sozo/src/commands/options/mod.rs +++ b/crates/sozo/src/commands/options/mod.rs @@ -1,3 +1,4 @@ pub mod account; pub mod starknet; +pub mod transaction; pub mod world; diff --git a/crates/sozo/src/commands/options/starknet.rs b/crates/sozo/src/commands/options/starknet.rs index 8986fb8aa2..10f151ee15 100644 --- a/crates/sozo/src/commands/options/starknet.rs +++ b/crates/sozo/src/commands/options/starknet.rs @@ -8,7 +8,7 @@ use url::Url; #[derive(Debug, Args)] #[command(next_help_heading = "Starknet options")] pub struct StarknetOptions { - #[arg(long)] + #[arg(long, env = "STARKNET_RPC_URL", default_value = "http://localhost:5050")] #[arg(value_name = "URL")] #[arg(help = "The Starknet RPC endpoint.")] pub rpc_url: Option, diff --git a/crates/sozo/src/commands/options/transaction.rs b/crates/sozo/src/commands/options/transaction.rs new file mode 100644 index 0000000000..e1e4a6a94b --- /dev/null +++ b/crates/sozo/src/commands/options/transaction.rs @@ -0,0 +1,20 @@ +use clap::Args; +use dojo_world::migration::TxConfig; + +#[derive(Debug, Args, Clone)] +#[command(next_help_heading = "Transaction options")] +pub struct TransactionOptions { + #[arg(long)] + #[arg(value_name = "MULTIPLIER")] + #[arg(help = "The multiplier to use for the fee estimate.")] + #[arg(long_help = "The multiplier to use for the fee estimate. This value will be used on \ + the estimated fee which will be used as the max fee for the transaction. \ + (max_fee = estimated_fee * multiplier)")] + pub fee_estimate_multiplier: Option, +} + +impl From for TxConfig { + fn from(value: TransactionOptions) -> Self { + Self { fee_estimate_multiplier: value.fee_estimate_multiplier } + } +} diff --git a/crates/sozo/src/commands/register.rs b/crates/sozo/src/commands/register.rs index 7ebb45dbc4..74cfa2ae22 100644 --- a/crates/sozo/src/commands/register.rs +++ b/crates/sozo/src/commands/register.rs @@ -17,31 +17,13 @@ pub struct RegisterArgs { #[derive(Debug, Subcommand)] pub enum RegisterCommand { - #[command(about = "Register a component to a world.")] - Component { + #[command(about = "Register a model to a world.")] + Model { #[arg(num_args = 1..)] #[arg(required = true)] #[arg(value_name = "CLASS_HASH")] - #[arg(help = "The class hash of the components to register.")] - components: Vec, - - #[command(flatten)] - world: WorldOptions, - - #[command(flatten)] - starknet: StarknetOptions, - - #[command(flatten)] - account: AccountOptions, - }, - - #[command(about = "Register a system to a world.")] - System { - #[arg(num_args = 1..)] - #[arg(required = true)] - #[arg(value_name = "CLASS_HASH")] - #[arg(help = "The class hash of the systems to register.")] - systems: Vec, + #[arg(help = "The class hash of the models to register.")] + models: Vec, #[command(flatten)] world: WorldOptions, diff --git a/crates/sozo/src/commands/scarb_internal/mod.rs b/crates/sozo/src/commands/scarb_internal/mod.rs new file mode 100644 index 0000000000..a7f3379580 --- /dev/null +++ b/crates/sozo/src/commands/scarb_internal/mod.rs @@ -0,0 +1,55 @@ +// I have copied source code from https://github.com/software-mansion/scarb/blob/main/scarb/src/compiler/db.rs +// since build_scarb_root_database is not public +// +// NOTE: This files needs to be updated whenever scarb version is updated +use anyhow::Result; +use cairo_lang_compiler::db::RootDatabase; +use cairo_lang_compiler::project::{ProjectConfig, ProjectConfigContent}; +use cairo_lang_filesystem::ids::Directory; +use scarb::compiler::CompilationUnit; +use scarb::core::Workspace; +use tracing::trace; + +// TODO(mkaput): ScarbDatabase? +pub(crate) fn build_scarb_root_database( + unit: &CompilationUnit, + ws: &Workspace<'_>, +) -> Result { + let mut b = RootDatabase::builder(); + b.with_project_config(build_project_config(unit)?); + b.with_cfg(unit.cfg_set.clone()); + + for plugin_info in &unit.cairo_plugins { + let package_id = plugin_info.package.id; + let plugin = ws.config().cairo_plugins().fetch(package_id)?; + let instance = plugin.instantiate()?; + for macro_plugin in instance.macro_plugins() { + b.with_macro_plugin(macro_plugin); + } + for (name, inline_macro_plugin) in instance.inline_macro_plugins() { + b.with_inline_macro_plugin(&name, inline_macro_plugin); + } + } + + b.build() +} + +fn build_project_config(unit: &CompilationUnit) -> Result { + let crate_roots = unit + .components + .iter() + .filter(|model| !model.package.id.is_core()) + .map(|model| (model.cairo_package_name(), model.target.source_root().into())) + .collect(); + + let corelib = Some(Directory::Real(unit.core_package_component().target.source_root().into())); + + let content = ProjectConfigContent { crate_roots }; + + let project_config = + ProjectConfig { base_path: unit.main_component().package.root().into(), corelib, content }; + + trace!(?project_config); + + Ok(project_config) +} diff --git a/crates/sozo/src/commands/system.rs b/crates/sozo/src/commands/system.rs deleted file mode 100644 index 74a7c484db..0000000000 --- a/crates/sozo/src/commands/system.rs +++ /dev/null @@ -1,61 +0,0 @@ -use anyhow::Result; -use clap::{Args, Subcommand}; -use dojo_world::metadata::dojo_metadata_from_workspace; -use scarb::core::Config; - -use super::options::starknet::StarknetOptions; -use super::options::world::WorldOptions; -use crate::ops::system; - -#[derive(Debug, Args)] -pub struct SystemArgs { - #[command(subcommand)] - command: SystemCommands, -} - -#[derive(Debug, Subcommand)] -pub enum SystemCommands { - #[command(about = "Get the class hash of a system.")] - Get { - #[arg(help = "The name of the system.")] - name: String, - - #[command(flatten)] - world: WorldOptions, - - #[command(flatten)] - starknet: StarknetOptions, - }, - - #[command(alias = "dep")] - #[command(about = "Retrieve the component dependencies of a system.")] - Dependency { - #[arg(help = "The name of the system.")] - name: String, - - #[arg(short = 'j', long = "json")] - #[arg(help_heading = "Display options")] - to_json: bool, - - #[command(flatten)] - world: WorldOptions, - - #[command(flatten)] - starknet: StarknetOptions, - }, -} - -impl SystemArgs { - pub fn run(self, config: &Config) -> Result<()> { - let env_metadata = if config.manifest_path().exists() { - let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; - - // TODO: Check the updated scarb way to read profile specific values - dojo_metadata_from_workspace(&ws).and_then(|inner| inner.env().cloned()) - } else { - None - }; - - config.tokio_handle().block_on(system::execute(self.command, env_metadata)) - } -} diff --git a/crates/sozo/src/commands/test.rs b/crates/sozo/src/commands/test.rs index 9793dce7d0..4bd21ce5d8 100644 --- a/crates/sozo/src/commands/test.rs +++ b/crates/sozo/src/commands/test.rs @@ -8,11 +8,15 @@ use cairo_lang_compiler::diagnostics::DiagnosticsReporter; use cairo_lang_compiler::project::{ProjectConfig, ProjectConfigContent}; use cairo_lang_filesystem::cfg::{Cfg, CfgSet}; use cairo_lang_filesystem::ids::Directory; +use cairo_lang_starknet::inline_macros::selector::SelectorMacro; use cairo_lang_starknet::plugin::StarkNetPlugin; use cairo_lang_test_runner::plugin::TestPlugin; use cairo_lang_test_runner::TestRunner; use clap::Args; use dojo_lang::compiler::{collect_core_crate_ids, collect_external_crate_ids, Props}; +use dojo_lang::inline_macros::emit::EmitMacro; +use dojo_lang::inline_macros::get::GetMacro; +use dojo_lang::inline_macros::set::SetMacro; use dojo_lang::plugin::DojoPlugin; use scarb::compiler::helpers::collect_main_crate_ids; use scarb::compiler::CompilationUnit; @@ -49,6 +53,7 @@ impl TestArgs { let db = build_root_database(&unit)?; let mut main_crate_ids = collect_main_crate_ids(&unit, &db); + let test_crate_ids = main_crate_ids.clone(); if unit.main_package_id.name.to_string() != "dojo" { let core_crate_ids = collect_core_crate_ids(&db); @@ -66,6 +71,7 @@ impl TestArgs { let runner = TestRunner { db, main_crate_ids, + test_crate_ids, filter: self.filter.clone(), include_ignored: self.include_ignored, ignored: self.ignored, @@ -85,9 +91,14 @@ pub(crate) fn build_root_database(unit: &CompilationUnit) -> Result Result { let crate_roots = unit .components .iter() - .filter(|component| !component.package.id.is_core()) - .map(|component| (component.cairo_package_name(), component.target.source_root().into())) + .filter(|model| !model.package.id.is_core()) + .map(|model| (model.cairo_package_name(), model.target.source_root().into())) .collect(); - let corelib = Some(Directory(unit.core_package_component().target.source_root().into())); + let corelib = Some(Directory::Real(unit.core_package_component().target.source_root().into())); let content = ProjectConfigContent { crate_roots }; diff --git a/crates/sozo/src/lib.rs b/crates/sozo/src/lib.rs new file mode 100644 index 0000000000..fc9ec51d87 --- /dev/null +++ b/crates/sozo/src/lib.rs @@ -0,0 +1,3 @@ +pub mod args; +pub mod commands; +pub mod ops; diff --git a/crates/sozo/src/main.rs b/crates/sozo/src/main.rs index 81199c352f..ac5d6c212d 100644 --- a/crates/sozo/src/main.rs +++ b/crates/sozo/src/main.rs @@ -7,13 +7,8 @@ use dojo_lang::compiler::DojoCompiler; use dojo_lang::plugin::CairoPluginRepository; use scarb::compiler::CompilerRepository; use scarb::core::Config; -use scarb::ui::{OutputFormat, Ui}; - -mod args; -mod commands; -mod ops; - -use args::{Commands, SozoArgs}; +use scarb_ui::{OutputFormat, Ui}; +use sozo::args::{Commands, SozoArgs}; fn main() { let args = SozoArgs::parse(); @@ -30,8 +25,9 @@ fn cli_main(args: SozoArgs) -> Result<()> { let mut compilers = CompilerRepository::std(); let cairo_plugins = CairoPluginRepository::new(); - if let Commands::Build(_) = &args.command { - compilers.add(Box::new(DojoCompiler)).unwrap(); + match &args.command { + Commands::Build(_) | Commands::Dev(_) => compilers.add(Box::new(DojoCompiler)).unwrap(), + _ => {} } let manifest_path = scarb::ops::find_manifest_path(args.manifest_path.as_deref())?; @@ -45,5 +41,5 @@ fn cli_main(args: SozoArgs) -> Result<()> { .compilers(compilers) .build()?; - commands::run(args.command, &config) + sozo::commands::run(args.command, &config) } diff --git a/crates/sozo/src/ops/auth.rs b/crates/sozo/src/ops/auth.rs index 11d4af7d51..9e0b45262a 100644 --- a/crates/sozo/src/ops/auth.rs +++ b/crates/sozo/src/ops/auth.rs @@ -6,7 +6,7 @@ use crate::commands::auth::AuthCommand; pub async fn execute(command: AuthCommand, env_metadata: Option) -> Result<()> { match command { - AuthCommand::Writer { component, system, world, starknet, account } => { + AuthCommand::Writer { model, system, world, starknet, account } => { let world_address = world.address(env_metadata.as_ref())?; let provider = starknet.provider(env_metadata.as_ref())?; @@ -14,7 +14,7 @@ pub async fn execute(command: AuthCommand, env_metadata: Option) -> let world = WorldContract::new(world_address, &account); let res = world - .grant_writer(&component, &system) + .grant_writer(&model, &system) .await .with_context(|| "Failed to send transaction")?; diff --git a/crates/sozo/src/ops/component.rs b/crates/sozo/src/ops/component.rs deleted file mode 100644 index 3ce64ae93d..0000000000 --- a/crates/sozo/src/ops/component.rs +++ /dev/null @@ -1,64 +0,0 @@ -use anyhow::Result; -use dojo_world::metadata::Environment; -use starknet::core::types::{BlockId, BlockTag}; -use torii_client::contract::world::WorldContractReader; - -use crate::commands::component::ComponentCommands; - -pub async fn execute(command: ComponentCommands, env_metadata: Option) -> Result<()> { - match command { - ComponentCommands::Get { name, world, starknet } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider); - let component = world.component(&name, BlockId::Tag(BlockTag::Pending)).await?; - - println!("{:#x}", component.class_hash()); - } - - ComponentCommands::Schema { name, world, starknet, to_json } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider); - let component = world.component(&name, BlockId::Tag(BlockTag::Pending)).await?; - - let schema = component.schema(BlockId::Tag(BlockTag::Pending)).await?; - - if to_json { - println!("{}", serde_json::to_string_pretty(&schema)?) - } else { - let output = format!( - r"struct {name} {{ -{} -}}", - schema - .iter() - .map(|s| format!(r" {}: {}", s.name, s.ty)) - .collect::>() - .join("\n") - ); - - println!("{output}") - } - } - - ComponentCommands::Entity { name, keys, starknet, world, .. } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider); - let component = world.component(&name, BlockId::Tag(BlockTag::Pending)).await?; - - let entity = component.entity(keys, BlockId::Tag(BlockTag::Pending)).await?; - - println!( - "{}", - entity.iter().map(|f| format!("{f:#x}")).collect::>().join("\n") - ) - } - } - - Ok(()) -} diff --git a/crates/sozo/src/ops/events.rs b/crates/sozo/src/ops/events.rs index 2f560696ac..02aa24e260 100644 --- a/crates/sozo/src/ops/events.rs +++ b/crates/sozo/src/ops/events.rs @@ -1,12 +1,20 @@ +use std::collections::{HashMap, VecDeque}; + use anyhow::Result; +use cairo_lang_starknet::abi::{Event, EventKind}; +use cairo_lang_starknet::plugin::events::EventFieldKind; use dojo_world::metadata::Environment; use starknet::core::types::{BlockId, EventFilter}; -use starknet::core::utils::starknet_keccak; +use starknet::core::utils::{parse_cairo_short_string, starknet_keccak}; use starknet::providers::Provider; use crate::commands::events::EventsArgs; -pub async fn execute(args: EventsArgs, env_metadata: Option) -> Result<()> { +pub async fn execute( + args: EventsArgs, + env_metadata: Option, + events_map: HashMap>, +) -> Result<()> { let EventsArgs { chunk_size, starknet, @@ -15,6 +23,7 @@ pub async fn execute(args: EventsArgs, env_metadata: Option) -> Res to_block, events, continuation_token, + json, } = args; let from_block = from_block.map(BlockId::Number); @@ -29,7 +38,125 @@ pub async fn execute(args: EventsArgs, env_metadata: Option) -> Res let res = provider.get_events(event_filter, continuation_token, chunk_size).await?; - let value = serde_json::to_value(res)?; - println!("{}", serde_json::to_string_pretty(&value)?); + if json { + let value = serde_json::to_value(res)?; + println!("{}", serde_json::to_string_pretty(&value)?); + } else { + parse_and_print_events(res, events_map); + } Ok(()) } + +fn parse_and_print_events( + res: starknet::core::types::EventsPage, + events_map: HashMap>, +) { + println!("Continuation token: {:?}", res.continuation_token); + println!("----------------------------------------------"); + for event in res.events { + if let Some(e) = parse_event(event.clone(), &events_map) { + println!("{}\n", e); + } else { + // Couldn't parse event + println!("{}\n", serde_json::to_string_pretty(&event).unwrap()); + } + } +} + +fn parse_event( + event: starknet::core::types::EmittedEvent, + events_map: &HashMap>, +) -> Option { + let keys = event.keys; + let event_hash = keys[0].to_string(); + let Some(events) = events_map.get(&event_hash) else { return None }; + + 'outer: for e in events { + let mut ret = format!("Event name: {}\n", e.name); + let mut data = VecDeque::from(event.data.clone()); + + // Length is two only when its custom event + if keys.len() == 2 { + let name = parse_cairo_short_string(&keys[1]).ok()?; + ret.push_str(&format!("Model name: {}\n", name)); + } + + match &e.kind { + EventKind::Struct { members } => { + for field in members { + if field.kind != EventFieldKind::DataSerde { + continue; + } + match field.ty.as_str() { + "core::starknet::contract_address::ContractAddress" + | "core::starknet::class_hash::ClassHash" => { + let value = match data.pop_front() { + Some(addr) => addr, + None => continue 'outer, + }; + ret.push_str(&format!("{}: {:#x}\n", field.name, value)); + } + "core::felt252" => { + let value = match data.pop_front() { + Some(addr) => addr, + None => continue 'outer, + }; + let value = match parse_cairo_short_string(&value) { + Ok(v) => v, + Err(_) => format!("{:#x}", value), + }; + ret.push_str(&format!("{}: {}\n", field.name, value)); + } + "core::integer::u8" => { + let value = match data.pop_front() { + Some(addr) => addr, + None => continue 'outer, + }; + let num = match value.to_string().parse::() { + Ok(num) => num, + Err(_) => continue 'outer, + }; + + ret.push_str(&format!("{}: {}\n", field.name, num)); + } + "dojo_examples::systems::move::Direction" => { + let value = match data.pop_front() { + Some(addr) => addr, + None => continue 'outer, + }; + ret.push_str(&format!("{}: {}\n", field.name, value)); + } + "core::array::Span::" => { + let length = match data.pop_front() { + Some(addr) => addr, + None => continue 'outer, + }; + let length = match length.to_string().parse::() { + Ok(len) => len, + Err(_) => continue 'outer, + }; + ret.push_str(&format!("{}: ", field.name)); + if data.len() >= length { + ret.push_str(&format!( + "{:?}\n", + data.drain(..length) + .map(|e| format!("{:#x}", e)) + .collect::>() + )); + } else { + continue 'outer; + } + } + _ => { + return None; + } + } + } + return Some(ret); + } + EventKind::Enum { .. } => unreachable!("shouldn't reach here"), + } + } + + None +} diff --git a/crates/sozo/src/ops/execute.rs b/crates/sozo/src/ops/execute.rs index 8dbda3b1e4..3d8fe31a64 100644 --- a/crates/sozo/src/ops/execute.rs +++ b/crates/sozo/src/ops/execute.rs @@ -1,20 +1,26 @@ use anyhow::{Context, Result}; use dojo_world::metadata::Environment; -use torii_client::contract::world::WorldContract; +use starknet::accounts::{Account, Call}; +use starknet::core::utils::get_selector_from_name; use crate::commands::execute::ExecuteArgs; pub async fn execute(args: ExecuteArgs, env_metadata: Option) -> Result<()> { - let ExecuteArgs { system, calldata, world, starknet, account } = args; + let ExecuteArgs { contract, entrypoint, calldata, starknet, account } = args; - let world_address = world.address(env_metadata.as_ref())?; let provider = starknet.provider(env_metadata.as_ref())?; let account = account.account(provider, env_metadata.as_ref()).await?; - let world = WorldContract::new(world_address, &account); - let res = - world.execute(&system, calldata).await.with_context(|| "Failed to send transaction")?; + let res = account + .execute(vec![Call { + calldata, + to: contract, + selector: get_selector_from_name(&entrypoint).unwrap(), + }]) + .send() + .await + .with_context(|| "Failed to send transaction")?; println!("Transaction: {:#x}", res.transaction_hash); diff --git a/crates/sozo/src/ops/migration/migration_test.rs b/crates/sozo/src/ops/migration/migration_test.rs index e5afd2f697..b8f9a6acd4 100644 --- a/crates/sozo/src/ops/migration/migration_test.rs +++ b/crates/sozo/src/ops/migration/migration_test.rs @@ -1,64 +1,65 @@ use camino::Utf8PathBuf; +use dojo_test_utils::migration::prepare_migration; use dojo_test_utils::sequencer::{ - get_default_test_starknet_config, SequencerConfig, TestSequencer, + get_default_test_starknet_config, SequencerConfig, StarknetConfig, TestSequencer, }; use dojo_world::manifest::Manifest; use dojo_world::migration::strategy::prepare_for_migration; use dojo_world::migration::world::WorldDiff; -use scarb::core::Config; -use scarb::ui::Verbosity; -use starknet::accounts::SingleOwnerAccount; +use scarb_ui::{OutputFormat, Ui, Verbosity}; +use starknet::accounts::{ExecutionEncoding, SingleOwnerAccount}; use starknet::core::chain_id; -use starknet::core::types::FieldElement; +use starknet::core::types::{BlockId, BlockTag}; +use starknet::macros::felt; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use starknet::signers::{LocalWallet, SigningKey}; +use crate::commands::options::transaction::TransactionOptions; use crate::ops::migration::execute_strategy; -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn migrate_with_auto_mine() { - let target_dir = Utf8PathBuf::from_path_buf("../../examples/ecs/target/dev".into()).unwrap(); + let ui = Ui::new(Verbosity::Verbose, OutputFormat::Text); + let migration = prepare_migration("../../examples/ecs/target/dev".into()).unwrap(); let sequencer = TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; - let account = SingleOwnerAccount::new( - JsonRpcClient::new(HttpTransport::new(sequencer.url())), - LocalWallet::from_signing_key(SigningKey::from_secret_scalar( - sequencer.raw_account().private_key, - )), - sequencer.raw_account().account_address, - chain_id::TESTNET, - ); + let mut account = sequencer.account(); + account.set_block_id(BlockId::Tag(BlockTag::Pending)); - let config = Config::builder(Utf8PathBuf::from_path_buf("../../examples/ecs/".into()).unwrap()) - .ui_verbosity(Verbosity::Quiet) - .build() - .unwrap(); + execute_strategy(&migration, &account, &ui, None).await.unwrap(); - let manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); - let world = WorldDiff::compute(manifest, None); + sequencer.stop().unwrap(); +} - let migration = prepare_for_migration( - None, - Some(FieldElement::from_hex_be("0x12345").unwrap()), - target_dir, - world, +#[tokio::test(flavor = "multi_thread")] +async fn migrate_with_block_time() { + let ui = Ui::new(Verbosity::Verbose, OutputFormat::Text); + let migration = prepare_migration("../../examples/ecs/target/dev".into()).unwrap(); + + let sequencer = TestSequencer::start( + SequencerConfig { block_time: Some(1000), ..Default::default() }, + get_default_test_starknet_config(), ) - .unwrap(); - execute_strategy(&migration, &account, &config).await.unwrap(); + .await; + + let mut account = sequencer.account(); + account.set_block_id(BlockId::Tag(BlockTag::Pending)); + execute_strategy(&migration, &account, &ui, None).await.unwrap(); sequencer.stop().unwrap(); } -#[tokio::test] -async fn migrate_with_block_time() { - let target_dir = Utf8PathBuf::from_path_buf("../../examples/ecs/target/dev".into()).unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn migrate_with_small_fee_multiplier_will_fail() { + let ui = Ui::new(Verbosity::Verbose, OutputFormat::Text); + let migration = prepare_migration("../../examples/ecs/target/dev".into()).unwrap(); let sequencer = TestSequencer::start( - SequencerConfig { block_time: Some(1) }, - get_default_test_starknet_config(), + Default::default(), + StarknetConfig { disable_fee: false, ..Default::default() }, ) .await; @@ -69,25 +70,19 @@ async fn migrate_with_block_time() { )), sequencer.raw_account().account_address, chain_id::TESTNET, + ExecutionEncoding::Legacy, ); - let config = Config::builder(Utf8PathBuf::from_path_buf("../../examples/ecs/".into()).unwrap()) - .ui_verbosity(Verbosity::Quiet) - .build() - .unwrap(); - - let manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); - let world = WorldDiff::compute(manifest, None); - - let migration = prepare_for_migration( - None, - Some(FieldElement::from_hex_be("0x12345").unwrap()), - target_dir, - world, - ) - .unwrap(); - execute_strategy(&migration, &account, &config).await.unwrap(); - + assert!( + execute_strategy( + &migration, + &account, + &ui, + Some(TransactionOptions { fee_estimate_multiplier: Some(0.2f64) }), + ) + .await + .is_err() + ); sequencer.stop().unwrap(); } @@ -103,6 +98,7 @@ fn migrate_world_without_seed_will_fail() { #[ignore] #[tokio::test] async fn migration_from_remote() { + let ui = Ui::new(Verbosity::Verbose, OutputFormat::Text); let target_dir = Utf8PathBuf::from_path_buf("../../examples/ecs/target/dev".into()).unwrap(); let sequencer = @@ -115,25 +111,16 @@ async fn migration_from_remote() { )), sequencer.raw_account().account_address, chain_id::TESTNET, + ExecutionEncoding::Legacy, ); - let config = Config::builder(Utf8PathBuf::from_path_buf("../../examples/ecs/".into()).unwrap()) - .ui_verbosity(Verbosity::Quiet) - .build() - .unwrap(); - let manifest = Manifest::load_from_path(target_dir.clone()).unwrap(); let world = WorldDiff::compute(manifest, None); - let migration = prepare_for_migration( - None, - Some(FieldElement::from_hex_be("0x12345").unwrap()), - target_dir.clone(), - world, - ) - .unwrap(); + let migration = + prepare_for_migration(None, Some(felt!("0x12345")), target_dir.clone(), world).unwrap(); - execute_strategy(&migration, &account, &config).await.unwrap(); + execute_strategy(&migration, &account, &ui, None).await.unwrap(); let local_manifest = Manifest::load_from_path(target_dir.join("manifest.json")).unwrap(); let remote_manifest = Manifest::from_remote( @@ -148,6 +135,6 @@ async fn migration_from_remote() { assert_eq!(local_manifest.world.class_hash, remote_manifest.world.class_hash); assert_eq!(local_manifest.executor.class_hash, remote_manifest.executor.class_hash); - assert_eq!(local_manifest.components.len(), remote_manifest.components.len()); + assert_eq!(local_manifest.models.len(), remote_manifest.models.len()); assert_eq!(local_manifest.systems.len(), remote_manifest.systems.len()); } diff --git a/crates/sozo/src/ops/migration/mod.rs b/crates/sozo/src/ops/migration/mod.rs index 7d22b8d5fd..e69f8ce41d 100644 --- a/crates/sozo/src/ops/migration/mod.rs +++ b/crates/sozo/src/ops/migration/mod.rs @@ -3,11 +3,15 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use dojo_world::manifest::{Manifest, ManifestError}; use dojo_world::metadata::Environment; +use dojo_world::migration::contract::ContractMigration; use dojo_world::migration::strategy::{prepare_for_migration, MigrationStrategy}; use dojo_world::migration::world::WorldDiff; -use dojo_world::migration::{Declarable, Deployable, MigrationError, RegisterOutput, StateDiff}; +use dojo_world::migration::{ + Declarable, DeployOutput, Deployable, MigrationError, RegisterOutput, StateDiff, +}; use dojo_world::utils::TransactionWaiter; use scarb::core::Config; +use scarb_ui::Ui; use starknet::accounts::{Account, ConnectedAccount, SingleOwnerAccount}; use starknet::core::types::{ BlockId, BlockTag, FieldElement, InvokeTransactionResult, StarknetError, @@ -31,6 +35,7 @@ use self::ui::{bold_message, italic_message}; use crate::commands::migrate::MigrateArgs; use crate::commands::options::account::AccountOptions; use crate::commands::options::starknet::StarknetOptions; +use crate::commands::options::transaction::TransactionOptions; use crate::commands::options::world::WorldOptions; pub async fn execute( @@ -64,41 +69,60 @@ where if total_diffs == 0 { config.ui().print("\n✨ No changes to be made. Remote World is already up to date!") } else { - // Prepare migration strategy based on the diff. + // Mirate according to the diff. + apply_diff(target_dir, diff, name, world_address, &account, config, Some(args.transaction)) + .await?; + } - let strategy = prepare_migration(target_dir, diff, name, world_address, config)?; + Ok(()) +} - println!(" "); +pub(crate) async fn apply_diff( + target_dir: U, + diff: WorldDiff, + name: Option, + world_address: Option, + account: &SingleOwnerAccount, + config: &Config, + txn_config: Option, +) -> Result +where + U: AsRef, + P: Provider + Sync + Send + 'static, + S: Signer + Sync + Send + 'static, +{ + let strategy = prepare_migration(target_dir, diff, name, world_address, config)?; - let block_height = execute_strategy(&strategy, &account, config) - .await - .map_err(|e| anyhow!(e)) - .with_context(|| "Problem trying to migrate.")?; - - if let Some(block_height) = block_height { - config.ui().print(format!( - "\n🎉 Successfully migrated World on block #{} at address {}", - block_height, - bold_message(format!( - "{:#x}", - strategy.world_address().expect("world address must exist") - )) - )); - } else { - config.ui().print(format!( - "\n🎉 Successfully migrated World at address {}", - bold_message(format!( - "{:#x}", - strategy.world_address().expect("world address must exist") - )) - )); - } + println!(" "); + + let block_height = execute_strategy(&strategy, account, config.ui(), txn_config) + .await + .map_err(|e| anyhow!(e)) + .with_context(|| "Problem trying to migrate.")?; + + if let Some(block_height) = block_height { + config.ui().print(format!( + "\n🎉 Successfully migrated World on block #{} at address {}", + block_height, + bold_message(format!( + "{:#x}", + strategy.world_address().expect("world address must exist") + )) + )); + } else { + config.ui().print(format!( + "\n🎉 Successfully migrated World at address {}", + bold_message(format!( + "{:#x}", + strategy.world_address().expect("world address must exist") + )) + )); } - Ok(()) + strategy.world_address() } -async fn setup_env( +pub(crate) async fn setup_env( account: AccountOptions, starknet: StarknetOptions, world: WorldOptions, @@ -216,39 +240,20 @@ where // returns the Some(block number) at which migration world is deployed, returns none if world was // not redeployed -async fn execute_strategy( +pub async fn execute_strategy( strategy: &MigrationStrategy, migrator: &SingleOwnerAccount, - ws_config: &Config, + ui: &Ui, + txn_config: Option, ) -> Result> where P: Provider + Sync + Send + 'static, S: Signer + Sync + Send + 'static, { - let mut block_height = None; match &strategy.executor { Some(executor) => { - ws_config.ui().print_header("# Executor"); - - match executor.deploy(executor.diff.local, vec![], migrator).await { - Ok(val) => { - if let Some(declare) = val.clone().declare { - ws_config.ui().print_hidden_sub(format!( - "Declare transaction: {:#x}", - declare.transaction_hash - )); - } - - ws_config.ui().print_hidden_sub(format!( - "Deploy transaction: {:#x}", - val.transaction_hash - )); - - Ok(()) - } - Err(MigrationError::ContractAlreadyDeployed) => Ok(()), - Err(e) => Err(anyhow!("Failed to migrate executor: {:?}", e)), - }?; + ui.print_header("# Executor"); + deploy_contract(executor, "executor", vec![], migrator, ui, &txn_config).await?; if strategy.world.is_none() { let addr = strategy.world_address()?; @@ -257,193 +262,173 @@ where .set_executor(executor.contract_address) .await?; - let _ = - TransactionWaiter::new(transaction_hash, migrator.provider()).await.map_err( - MigrationError::< - as Account>::SignError, -

::Error, - >::WaitingError, - )?; + TransactionWaiter::new(transaction_hash, migrator.provider()).await?; - ws_config.ui().print_hidden_sub(format!("Updated at: {transaction_hash:#x}")); + ui.print_hidden_sub(format!("Updated at: {transaction_hash:#x}")); } - ws_config.ui().print_sub(format!("Contract address: {:#x}", executor.contract_address)); + ui.print_sub(format!("Contract address: {:#x}", executor.contract_address)); } None => {} }; match &strategy.world { Some(world) => { - ws_config.ui().print_header("# World"); - - match world - .deploy( - world.diff.local, - vec![strategy.executor.as_ref().unwrap().contract_address], - migrator, - ) - .await - { - Ok(val) => { - if let Some(declare) = val.clone().declare { - ws_config.ui().print_hidden_sub(format!( - "Declare transaction: {:#x}", - declare.transaction_hash - )); - } - - ws_config.ui().print_hidden_sub(format!( - "Deploy transaction: {:#x}", - val.transaction_hash - )); - - block_height = Some(val.block_number); - - Ok(()) - } - Err(MigrationError::ContractAlreadyDeployed) => { - ws_config.ui().print_sub("World already exists, updating..."); - Ok(()) - } - Err(e) => Err(anyhow!("Failed to migrate world: {:?}", e)), - }?; + ui.print_header("# World"); + let calldata = vec![strategy.executor.as_ref().unwrap().contract_address]; + deploy_contract(world, "world", calldata, migrator, ui, &txn_config).await?; - ws_config.ui().print_sub(format!("Contract address: {:#x}", world.contract_address)); + ui.print_sub(format!("Contract address: {:#x}", world.contract_address)); } None => {} }; - register_components(strategy, migrator, ws_config).await?; - register_systems(strategy, migrator, ws_config).await?; + register_models(strategy, migrator, ui, txn_config.clone()).await?; + deploy_contracts(strategy, migrator, ui, txn_config).await?; + + // This gets current block numder if helpful + // let block_height = migrator.provider().block_number().await.ok(); + + Ok(None) +} + +enum ContractDeploymentOutput { + AlreadyDeployed(FieldElement), + Output(DeployOutput), +} + +async fn deploy_contract( + contract: &ContractMigration, + contract_id: &str, + constructor_calldata: Vec, + migrator: &SingleOwnerAccount, + ui: &Ui, + txn_config: &Option, +) -> Result +where + P: Provider + Sync + Send + 'static, + S: Signer + Sync + Send + 'static, +{ + match contract + .deploy( + contract.diff.local, + constructor_calldata, + migrator, + txn_config.clone().map(|c| c.into()).unwrap_or_default(), + ) + .await + { + Ok(val) => { + if let Some(declare) = val.clone().declare { + ui.print_hidden_sub(format!( + "Declare transaction: {:#x}", + declare.transaction_hash + )); + } + + ui.print_hidden_sub(format!("Deploy transaction: {:#x}", val.transaction_hash)); - Ok(block_height) + Ok(ContractDeploymentOutput::Output(val)) + } + Err(MigrationError::ContractAlreadyDeployed(contract_address)) => { + Ok(ContractDeploymentOutput::AlreadyDeployed(contract_address)) + } + Err(e) => Err(anyhow!("Failed to migrate {}: {:?}", contract_id, e)), + } } -async fn register_components( +async fn register_models( strategy: &MigrationStrategy, migrator: &SingleOwnerAccount, - ws_config: &Config, + ui: &Ui, + txn_config: Option, ) -> Result> where P: Provider + Sync + Send + 'static, S: Signer + Sync + Send + 'static, { - let components = &strategy.components; + let models = &strategy.models; - if components.is_empty() { + if models.is_empty() { return Ok(None); } - ws_config.ui().print_header(format!("# Components ({})", components.len())); + ui.print_header(format!("# Models ({})", models.len())); let mut declare_output = vec![]; - for c in components.iter() { - ws_config.ui().print(italic_message(&c.diff.name).to_string()); + for c in models.iter() { + ui.print(italic_message(&c.diff.name).to_string()); - let res = c.declare(migrator).await; + let res = + c.declare(migrator, txn_config.clone().map(|c| c.into()).unwrap_or_default()).await; match res { Ok(output) => { - ws_config.ui().print_hidden_sub(format!( - "declare transaction: {:#x}", - output.transaction_hash - )); + ui.print_hidden_sub(format!("transaction_hash: {:#x}", output.transaction_hash)); declare_output.push(output); } - // Continue if component is already declared + // Continue if model is already declared Err(MigrationError::ClassAlreadyDeclared) => { - ws_config.ui().print_sub("Already declared"); + ui.print_sub("Already declared"); continue; } - Err(e) => bail!("Failed to declare component {}: {e}", c.diff.name), + Err(e) => bail!("Failed to declare model {}: {e}", c.diff.name), } - ws_config.ui().print_sub(format!("class hash: {:#x}", c.diff.local)); + ui.print_sub(format!("Class hash: {:#x}", c.diff.local)); } let world_address = strategy.world_address()?; let InvokeTransactionResult { transaction_hash } = WorldContract::new(world_address, migrator) - .register_components(&components.iter().map(|c| c.diff.local).collect::>()) + .register_models(&models.iter().map(|c| c.diff.local).collect::>()) .await - .map_err(|e| anyhow!("Failed to register components to World: {e}"))?; + .map_err(|e| anyhow!("Failed to register models to World: {e}"))?; - let _ = - TransactionWaiter::new(transaction_hash, migrator.provider()).await.map_err( - MigrationError::< - as Account>::SignError, -

::Error, - >::WaitingError, - )?; + TransactionWaiter::new(transaction_hash, migrator.provider()).await?; - ws_config.ui().print_hidden_sub(format!("registered at: {transaction_hash:#x}")); + ui.print_hidden_sub(format!("registered at: {transaction_hash:#x}")); Ok(Some(RegisterOutput { transaction_hash, declare_output })) } -async fn register_systems( +async fn deploy_contracts( strategy: &MigrationStrategy, migrator: &SingleOwnerAccount, - ws_config: &Config, -) -> Result> + ui: &Ui, + txn_config: Option, +) -> Result>> where P: Provider + Sync + Send + 'static, S: Signer + Sync + Send + 'static, { - let systems = &strategy.systems; + let contracts = &strategy.contracts; - if systems.is_empty() { - return Ok(None); + if contracts.is_empty() { + return Ok(vec![]); } - ws_config.ui().print_header(format!("# Systems ({})", systems.len())); + ui.print_header(format!("# Contracts ({})", contracts.len())); - let mut declare_output = vec![]; - - for s in strategy.systems.iter() { - ws_config.ui().print(italic_message(&s.diff.name).to_string()); + let mut deploy_output = vec![]; - let res = s.declare(migrator).await; - match res { - Ok(output) => { - ws_config.ui().print_hidden_sub(format!( - "Declare transaction: {:#x}", - output.transaction_hash - )); - - declare_output.push(output); + for contract in strategy.contracts.iter() { + let name = &contract.diff.name; + ui.print(italic_message(name).to_string()); + match deploy_contract(contract, name, vec![], migrator, ui, &txn_config).await? { + ContractDeploymentOutput::Output(output) => { + ui.print_sub(format!("Contract address: {:#x}", output.contract_address)); + ui.print_hidden_sub(format!("deploy transaction: {:#x}", output.transaction_hash)); + deploy_output.push(Some(output)); } - - // Continue if system is already declared - Err(MigrationError::ClassAlreadyDeclared) => { - ws_config.ui().print_sub("Already declared"); - continue; + ContractDeploymentOutput::AlreadyDeployed(contract_address) => { + ui.print_sub(format!("Already deployed: {:#x}", contract_address)); + deploy_output.push(None); } - Err(e) => bail!("Failed to declare system {}: {e}", s.diff.name), } - - ws_config.ui().print_sub(format!("class hash: {:#x}", s.diff.local)); } - let world_address = strategy.world_address()?; - - let InvokeTransactionResult { transaction_hash } = WorldContract::new(world_address, migrator) - .register_systems(&systems.iter().map(|s| s.diff.local).collect::>()) - .await - .map_err(|e| anyhow!("Failed to register systems to World: {e}"))?; - - let _ = - TransactionWaiter::new(transaction_hash, migrator.provider()).await.map_err( - MigrationError::< - as Account>::SignError, -

::Error, - >::WaitingError, - )?; - - ws_config.ui().print_hidden_sub(format!("registered at: {transaction_hash:#x}")); - - Ok(Some(RegisterOutput { transaction_hash, declare_output })) + Ok(deploy_output) } diff --git a/crates/sozo/src/ops/migration/ui.rs b/crates/sozo/src/ops/migration/ui.rs index b20b14728e..7ba22923fb 100644 --- a/crates/sozo/src/ops/migration/ui.rs +++ b/crates/sozo/src/ops/migration/ui.rs @@ -1,5 +1,5 @@ use console::{pad_str, Alignment, Style, StyledObject}; -use scarb::ui::Ui; +use scarb_ui::Ui; pub trait MigrationUi { fn print_step(&self, step: usize, icon: &str, message: &str); diff --git a/crates/sozo/src/ops/mod.rs b/crates/sozo/src/ops/mod.rs index c1e215ddfb..abf4eb9c5c 100644 --- a/crates/sozo/src/ops/mod.rs +++ b/crates/sozo/src/ops/mod.rs @@ -1,7 +1,6 @@ pub mod auth; -pub mod component; pub mod events; pub mod execute; pub mod migration; +pub mod model; pub mod register; -pub mod system; diff --git a/crates/sozo/src/ops/model.rs b/crates/sozo/src/ops/model.rs new file mode 100644 index 0000000000..b06bcf8b7b --- /dev/null +++ b/crates/sozo/src/ops/model.rs @@ -0,0 +1,53 @@ +use anyhow::Result; +use dojo_world::metadata::Environment; +use starknet::core::types::{BlockId, BlockTag}; +use torii_client::contract::world::WorldContractReader; + +use crate::commands::model::ModelCommands; + +pub async fn execute(command: ModelCommands, env_metadata: Option) -> Result<()> { + match command { + ModelCommands::ClassHash { name, world, starknet } => { + let world_address = world.address(env_metadata.as_ref())?; + let provider = starknet.provider(env_metadata.as_ref())?; + + let world = WorldContractReader::new(world_address, &provider); + let model = world.model(&name, BlockId::Tag(BlockTag::Pending)).await?; + + println!("{:#x}", model.class_hash()); + } + + ModelCommands::Schema { name, world, starknet, to_json } => { + let world_address = world.address(env_metadata.as_ref())?; + let provider = starknet.provider(env_metadata.as_ref())?; + + let world = WorldContractReader::new(world_address, &provider); + let model = world.model(&name, BlockId::Tag(BlockTag::Pending)).await?; + + let schema = model.schema(BlockId::Tag(BlockTag::Pending)).await?; + + if to_json { + println!("{}", serde_json::to_string_pretty(&schema)?) + } else { + println!("{schema}"); + } + } + + ModelCommands::Entity { name, keys, starknet, world, .. } => { + let world_address = world.address(env_metadata.as_ref())?; + let provider = starknet.provider(env_metadata.as_ref())?; + + let world = WorldContractReader::new(world_address, &provider); + let model = world.model(&name, BlockId::Tag(BlockTag::Pending)).await?; + + let entity = model.entity(keys, BlockId::Tag(BlockTag::Pending)).await?; + + println!( + "{}", + entity.iter().map(|f| format!("{f:#x}")).collect::>().join("\n") + ) + } + } + + Ok(()) +} diff --git a/crates/sozo/src/ops/register.rs b/crates/sozo/src/ops/register.rs index d5082989ff..738fa394a7 100644 --- a/crates/sozo/src/ops/register.rs +++ b/crates/sozo/src/ops/register.rs @@ -6,7 +6,7 @@ use crate::commands::register::RegisterCommand; pub async fn execute(command: RegisterCommand, env_metadata: Option) -> Result<()> { match command { - RegisterCommand::Component { components, world, starknet, account } => { + RegisterCommand::Model { models, world, starknet, account } => { let world_address = world.address(env_metadata.as_ref())?; let provider = starknet.provider(env_metadata.as_ref())?; @@ -14,26 +14,11 @@ pub async fn execute(command: RegisterCommand, env_metadata: Option let world = WorldContract::new(world_address, &account); let res = world - .register_components(&components) + .register_models(&models) .await .with_context(|| "Failed to send transaction")?; - println!("Components registered at transaction: {:#x}", res.transaction_hash) - } - - RegisterCommand::System { systems, world, starknet, account } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let account = account.account(provider, env_metadata.as_ref()).await?; - let world = WorldContract::new(world_address, &account); - - let res = world - .register_systems(&systems) - .await - .with_context(|| "Failed to send transaction")?; - - println!("Systems registered at transaction: {:#x}", res.transaction_hash) + println!("Models registered at transaction: {:#x}", res.transaction_hash) } } Ok(()) diff --git a/crates/sozo/src/ops/system.rs b/crates/sozo/src/ops/system.rs deleted file mode 100644 index 8b2612bbc3..0000000000 --- a/crates/sozo/src/ops/system.rs +++ /dev/null @@ -1,66 +0,0 @@ -use anyhow::Result; -use console::Style; -use dojo_world::metadata::Environment; -use starknet::core::types::{BlockId, BlockTag}; -use torii_client::contract::world::WorldContractReader; - -use crate::commands::system::SystemCommands; - -pub async fn execute(command: SystemCommands, env_metadata: Option) -> Result<()> { - match command { - SystemCommands::Get { name, world, starknet } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider); - let system = world.system(&name, BlockId::Tag(BlockTag::Pending)).await?; - - println!("{:#x}", system.class_hash()) - } - - SystemCommands::Dependency { name, to_json, world, starknet } => { - let world_address = world.address(env_metadata.as_ref())?; - let provider = starknet.provider(env_metadata.as_ref())?; - - let world = WorldContractReader::new(world_address, &provider); - let system = world.system(&name, BlockId::Tag(BlockTag::Pending)).await?; - - let deps = system.dependencies(BlockId::Tag(BlockTag::Pending)).await?; - - if to_json { - println!("{}", serde_json::to_string_pretty(&deps)?); - } else { - let read = deps - .iter() - .enumerate() - .filter_map(|(i, d)| { - if d.read { Some(format!("{}.{}", i + 1, d.name.clone())) } else { None } - }) - .collect::>(); - - let write = deps - .iter() - .enumerate() - .filter_map(|(i, d)| { - if d.write { Some(format!("{}. {}", i + 1, d.name.clone())) } else { None } - }) - .collect::>(); - - let output = format!( - r"{} -{} -{} -{}", - Style::from_dotted_str("bold.underlined").apply_to("Read:"), - read.join("\n"), - Style::from_dotted_str("bold.underlined").apply_to("Write:"), - write.join("\n"), - ); - - println!("{output}") - } - } - } - - Ok(()) -} diff --git a/crates/torii/client/Cargo.toml b/crates/torii/client/Cargo.toml index f5d3d75858..c1bc86f6c5 100644 --- a/crates/torii/client/Cargo.toml +++ b/crates/torii/client/Cargo.toml @@ -6,22 +6,32 @@ version = "0.2.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-std = { version = "1.12.0", default-features = false, features = [ "std" ] } +anyhow.workspace = true async-trait.workspace = true +crypto-bigint = "0.5.3" dojo-types = { path = "../../dojo-types" } -parking_lot = "0.12.1" +futures-util = "0.3.28" +futures.workspace = true +parking_lot.workspace = true +serde.workspace = true +serde_json.workspace = true starknet-crypto.workspace = true starknet.workspace = true thiserror.workspace = true +tokio = { version = "1.32.0", default-features = false, features = [ "rt" ] } +torii-grpc = { path = "../grpc", features = [ "client" ] } +url.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { version = "1.16", features = [ "time" ], default-features = false } +prost.workspace = true +tonic.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.64" -serde.workspace = true wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4.37" +wasm-prost.workspace = true +wasm-tonic.workspace = true web-sys = { version = "0.3.4", features = [ 'Window', 'WorkerGlobalScope' ] } [dev-dependencies] diff --git a/crates/torii/client/src/client/error.rs b/crates/torii/client/src/client/error.rs new file mode 100644 index 0000000000..50a052c60c --- /dev/null +++ b/crates/torii/client/src/client/error.rs @@ -0,0 +1,20 @@ +use starknet::core::types::FromStrError; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider}; + +use crate::contract::model::ModelError; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Error originated from the gRPC client. + #[error(transparent)] + GrpcClient(#[from] torii_grpc::client::Error), + #[error(transparent)] + FromStr(#[from] FromStrError), + #[error(transparent)] + Other(#[from] anyhow::Error), + #[error(transparent)] + UrlParse(#[from] url::ParseError), + #[error(transparent)] + Model(#[from] ModelError< as Provider>::Error>), +} diff --git a/crates/torii/client/src/client/mod.rs b/crates/torii/client/src/client/mod.rs new file mode 100644 index 0000000000..3494a671cb --- /dev/null +++ b/crates/torii/client/src/client/mod.rs @@ -0,0 +1,144 @@ +pub mod error; +pub mod storage; +pub mod subscription; + +use std::sync::Arc; + +use dojo_types::model::EntityModel; +use dojo_types::WorldMetadata; +use futures::channel::mpsc; +use parking_lot::{Mutex, RwLock}; +use starknet::core::types::{BlockId, BlockTag}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use starknet_crypto::FieldElement; +#[cfg(not(target_arch = "wasm32"))] +use tokio::task::spawn as spawn_task; +use url::Url; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_futures::spawn_local as spawn_task; + +use self::error::Error; +use self::storage::ComponentStorage; +use self::subscription::{SubscribedEntities, SubscriptionClientHandle}; +use crate::client::subscription::SubscriptionClient; +use crate::contract::world::WorldContractReader; + +// TODO: expose the World interface from the `Client` +#[allow(unused)] +pub struct Client { + /// Metadata of the World that the client is connected to. + metadata: Arc>, + /// The grpc client. + inner: Mutex, + /// Entity storage + storage: Arc, + /// Entities the client are subscribed to. + entity_subscription: Arc, + /// The subscription client handle + subscription_client_handle: SubscriptionClientHandle, +} + +impl Client { + /// Returns the metadata of the world. + pub fn world_metadata(&self) -> WorldMetadata { + self.metadata.read().clone() + } + + /// Returns the component value of an entity. + pub fn entity(&self, component: String, keys: Vec) -> Option> { + self.storage.get_entity((component, keys)) + } + + /// Returns the list of entities that the client is subscribed to. + pub fn synced_entities(&self) -> Vec { + self.entity_subscription.entities.read().clone().into_iter().collect() + } +} + +// TODO: able to handle entities that has not been set yet, currently `build` will panic if the +// `entities_to_sync` has never been set (the sql table isnt exist) +pub struct ClientBuilder { + initial_entities_to_sync: Option>, +} + +impl ClientBuilder { + #[must_use] + pub fn new() -> Self { + Self { initial_entities_to_sync: None } + } + + pub async fn build( + self, + torii_endpoint: String, + // TODO: remove RPC + rpc_url: String, + world: FieldElement, + ) -> Result { + let mut grpc_client = torii_grpc::client::WorldClient::new(torii_endpoint, world).await?; + + let metadata = grpc_client.metadata().await?; + + let shared_metadata: Arc<_> = RwLock::new(metadata).into(); + let client_storage: Arc<_> = ComponentStorage::new(shared_metadata.clone()).into(); + let subbed_entities: Arc<_> = SubscribedEntities::new(shared_metadata.clone()).into(); + + if let Some(entities_to_sync) = self.initial_entities_to_sync.clone() { + subbed_entities.add_entities(entities_to_sync)?; + + // initialize the entities to be synced with the latest values + let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(&rpc_url)?)); + let world_reader = WorldContractReader::new(world, &provider); + + // TODO: change this to querying the gRPC endpoint instead + let subbed_entities = subbed_entities.entities.read().clone(); + for EntityModel { model: component, keys } in subbed_entities { + let component_reader = + world_reader.model(&component, BlockId::Tag(BlockTag::Pending)).await?; + let values = + component_reader.entity(keys.clone(), BlockId::Tag(BlockTag::Pending)).await?; + client_storage.set_entity((component, keys), values)?; + } + } + + // initiate the stream any way, even if we don't have any initial entities to sync + let sub_res_stream = grpc_client + .subscribe_entities(self.initial_entities_to_sync.unwrap_or_default()) + .await?; + // setup the subscription client + let subscription_client_handle = { + let (sub_req_tx, sub_req_rcv) = mpsc::channel(128); + + spawn_task(SubscriptionClient { + sub_res_stream, + err_callback: None, + req_rcv: sub_req_rcv, + storage: client_storage.clone(), + world_metadata: shared_metadata.clone(), + subscribed_entities: subbed_entities.clone(), + }); + + SubscriptionClientHandle { event_handler: sub_req_tx } + }; + + Ok(Client { + storage: client_storage, + metadata: shared_metadata, + subscription_client_handle, + inner: Mutex::new(grpc_client), + entity_subscription: subbed_entities, + }) + } + + #[must_use] + pub fn set_entities_to_sync(mut self, entities: Vec) -> Self { + self.initial_entities_to_sync = Some(entities); + self + } +} + +impl Default for ClientBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/torii/client/src/client/storage.rs b/crates/torii/client/src/client/storage.rs new file mode 100644 index 0000000000..7c61e2e912 --- /dev/null +++ b/crates/torii/client/src/client/storage.rs @@ -0,0 +1,200 @@ +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; + +use anyhow::bail; +use dojo_types::WorldMetadata; +use parking_lot::RwLock; +use starknet::core::utils::cairo_short_string_to_felt; +use starknet::macros::short_string; +use starknet_crypto::{poseidon_hash_many, FieldElement}; + +pub type EntityKeys = Vec; + +pub type StorageKey = FieldElement; +pub type StorageValue = FieldElement; + +/// An in-memory storage for storing the component values of entities. +/// TODO: check if we can use sql db instead. +pub(crate) struct ComponentStorage { + metadata: Arc>, + // TODO: change entity id to entity keys + component_index: RwLock>>, /* component -> list of + * entity ids */ + pub(crate) storage: RwLock>, +} + +impl ComponentStorage { + pub(crate) fn new(metadata: Arc>) -> Self { + Self { metadata, storage: Default::default(), component_index: Default::default() } + } + + pub fn set_entity( + &self, + key: (String, Vec), + values: Vec, + ) -> anyhow::Result<()> { + let (component, entity_keys) = key; + + let Some(component_size) = self.metadata.read().components.get(&component).map(|c| c.size) + else { + bail!("unknown component: {component}") + }; + + if values.len().cmp(&(component_size as usize)).is_gt() { + bail!("too many values for component: {component}") + } else if values.len().cmp(&(component_size as usize)).is_lt() { + bail!("not enough values for component: {component}") + } + + let hashed_key = poseidon_hash_many(&entity_keys); + let entity_id = poseidon_hash_many(&[ + short_string!("dojo_storage"), + cairo_short_string_to_felt(&component).expect("valid cairo short string"), + hashed_key, + ]); + + self.component_index.write().entry(component).or_default().insert(entity_keys); + + values.into_iter().enumerate().for_each(|(i, value)| { + self.storage.write().insert(entity_id + i.into(), value); + }); + + Ok(()) + } + + pub fn get_entity(&self, key: (String, Vec)) -> Option> { + let (component, entity_keys) = key; + + let Some(component_size) = self.metadata.read().components.get(&component).map(|c| c.size) + else { + return None; + }; + + let hashed_key = poseidon_hash_many(&entity_keys); + let entity_id = poseidon_hash_many(&[ + short_string!("dojo_storage"), + cairo_short_string_to_felt(&component).expect("valid cairo short string"), + hashed_key, + ]); + + let mut values = Vec::with_capacity(component_size as usize); + + for i in 0..component_size { + let value = self.storage.read().get(&(entity_id + i.into())).copied()?; + values.push(value); + } + + Some(values) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::sync::Arc; + + use dojo_types::WorldMetadata; + use parking_lot::RwLock; + use starknet::core::utils::cairo_short_string_to_felt; + use starknet::macros::{felt, short_string}; + use starknet_crypto::poseidon_hash_many; + + fn create_dummy_metadata() -> WorldMetadata { + let components = HashMap::from([( + "Position".into(), + dojo_types::model::ModelMetadata { + name: "Position".into(), + class_hash: felt!("1"), + size: 3, + }, + )]); + + WorldMetadata { components, ..Default::default() } + } + + fn create_dummy_storage() -> super::ComponentStorage { + let metadata = Arc::new(RwLock::new(create_dummy_metadata())); + super::ComponentStorage::new(metadata) + } + + #[test] + fn err_if_set_values_too_many() { + let storage = create_dummy_storage(); + let entity = dojo_types::model::EntityModel { + model: "Position".into(), + keys: vec![felt!("0x12345")], + }; + + let values = vec![felt!("1"), felt!("2"), felt!("3"), felt!("4")]; + let result = storage.set_entity((entity.model, entity.keys), values); + + assert!(result.is_err()); + assert!(storage.storage.read().is_empty()); + } + + #[test] + fn err_if_set_values_too_few() { + let storage = create_dummy_storage(); + let entity = dojo_types::model::EntityModel { + model: "Position".into(), + keys: vec![felt!("0x12345")], + }; + + let values = vec![felt!("1"), felt!("2")]; + let result = storage.set_entity((entity.model, entity.keys), values); + + assert!(result.is_err()); + assert!(storage.storage.read().is_empty()); + } + + #[test] + fn set_and_get_entity_value() { + let storage = create_dummy_storage(); + let entity = dojo_types::model::EntityModel { + model: "Position".into(), + keys: vec![felt!("0x12345")], + }; + + assert!(storage.storage.read().is_empty()); + + let component = storage.metadata.read().components.get("Position").cloned().unwrap(); + + let expected_storage_addresses = { + let base = poseidon_hash_many(&[ + short_string!("dojo_storage"), + cairo_short_string_to_felt(&entity.model).unwrap(), + poseidon_hash_many(&entity.keys), + ]); + + (0..component.size).map(|i| base + i.into()).collect::>() + }; + + let expected_values = vec![felt!("1"), felt!("2"), felt!("3")]; + + storage + .set_entity((entity.model.clone(), entity.keys.clone()), expected_values.clone()) + .expect("set storage values"); + + let actual_values = + storage.get_entity((entity.model, entity.keys.clone())).expect("get storage values"); + + let actual_storage_addresses = + storage.storage.read().clone().into_keys().collect::>(); + + assert!( + storage + .component_index + .read() + .get("Position") + .is_some_and(|e| e.contains(&entity.keys)), + "entity keys must be indexed" + ); + assert!(actual_values == expected_values); + assert!(storage.storage.read().len() == component.size as usize); + assert!( + actual_storage_addresses + .into_iter() + .all(|address| expected_storage_addresses.contains(&address)) + ); + } +} diff --git a/crates/torii/client/src/client/subscription.rs b/crates/torii/client/src/client/subscription.rs new file mode 100644 index 0000000000..d42520a802 --- /dev/null +++ b/crates/torii/client/src/client/subscription.rs @@ -0,0 +1,274 @@ +use std::collections::HashSet; +use std::future::Future; +use std::str::FromStr; +use std::sync::Arc; +use std::task::Poll; + +use anyhow::{anyhow, bail, Result}; +use dojo_types::model::EntityModel; +use dojo_types::WorldMetadata; +use futures::channel::mpsc::{Receiver, Sender}; +use futures_util::StreamExt; +use parking_lot::RwLock; +use starknet::core::utils::cairo_short_string_to_felt; +use starknet::macros::short_string; +use starknet_crypto::{poseidon_hash_many, FieldElement}; +use torii_grpc::protos; +use torii_grpc::protos::types::EntityDiff; +use torii_grpc::protos::world::SubscribeEntitiesResponse; + +use super::ComponentStorage; + +#[derive(Debug, Clone)] +pub enum SubscriptionEvent { + SubscribeEntity(EntityModel), + UnsubscribeEntity(EntityModel), +} + +pub struct SubscribedEntities { + pub(super) entities: RwLock>, + /// All the relevant storage addresses derived from the subscribed entities + pub(super) subscribed_storage_addresses: RwLock>, + metadata: Arc>, +} + +impl SubscribedEntities { + pub fn new(metadata: Arc>) -> Self { + Self { + metadata, + entities: Default::default(), + subscribed_storage_addresses: Default::default(), + } + } + + pub fn add_entities(&self, entities: Vec) -> anyhow::Result<()> { + for entity in entities { + if !self.entities.write().insert(entity.clone()) { + continue; + } + + let hashed_key = poseidon_hash_many(&entity.keys); + let base_address = poseidon_hash_many(&[ + short_string!("dojo_storage"), + cairo_short_string_to_felt(&entity.model).map_err(|e| anyhow!(e))?, + hashed_key, + ]); + + let Some(component_size) = + self.metadata.read().components.get(&entity.model).map(|c| c.size) + else { + bail!("unknown component {}", entity.model) + }; + + (0..component_size).for_each(|i| { + self.subscribed_storage_addresses.write().insert(base_address + i.into()); + }); + } + + Ok(()) + } + + pub fn remove_entities(&self, entities: Vec) -> anyhow::Result<()> { + for entity in entities { + if !self.entities.write().remove(&entity) { + continue; + } + + let hashed_key = poseidon_hash_many(&entity.keys); + let base_address = poseidon_hash_many(&[ + short_string!("dojo_storage"), + cairo_short_string_to_felt(&entity.model).map_err(|e| anyhow!(e))?, + hashed_key, + ]); + + let Some(component_size) = + self.metadata.read().components.get(&entity.model).map(|c| c.size) + else { + bail!("unknown component {}", entity.model) + }; + + (0..component_size).for_each(|i| { + self.subscribed_storage_addresses.write().remove(&(base_address + i.into())); + }); + } + + Ok(()) + } +} + +#[allow(unused)] +pub(crate) struct SubscriptionClientHandle { + pub(super) event_handler: Sender, +} + +#[must_use = "SubscriptionClient does nothing unless polled"] +pub struct SubscriptionClient { + pub(super) req_rcv: Receiver, + /// The stream returned by the subscription server to receive the response + pub(super) sub_res_stream: tonic::Streaming, + /// Callback to be called on error + pub(super) err_callback: Option>, + + // for processing the entity diff and updating the storage + pub(super) storage: Arc, + pub(super) world_metadata: Arc>, + pub(super) subscribed_entities: Arc, +} + +impl SubscriptionClient { + // TODO: handle the subscription events properly + fn handle_event(&self, event: SubscriptionEvent) -> Result<()> { + match event { + SubscriptionEvent::SubscribeEntity(entity) => { + self.subscribed_entities.add_entities(vec![entity]) + } + SubscriptionEvent::UnsubscribeEntity(entity) => { + self.subscribed_entities.remove_entities(vec![entity]) + } + } + } + + // handle the response from the subscription stream + fn handle_response(&self, response: Result) { + match response { + Ok(res) => { + let entity_diff = res + .entity_update + .and_then(|e| e.update) + .and_then(|update| match update { + protos::types::maybe_pending_entity_update::Update::EntityUpdate( + update, + ) => update.entity_diff, + protos::types::maybe_pending_entity_update::Update::PendingEntityUpdate( + update, + ) => update.entity_diff, + }) + .expect("have entity update"); + + self.process_entity_diff(entity_diff); + } + + Err(err) => { + if let Some(ref callback) = self.err_callback { + callback(err) + } + } + } + } + + fn process_entity_diff(&self, diff: EntityDiff) { + let storage_entries = diff.storage_diffs.into_iter().find_map(|d| { + let expected = self.world_metadata.read().world_address; + let current = FieldElement::from_str(&d.address).expect("valid FieldElement value"); + if current == expected { Some(d.storage_entries) } else { None } + }); + + let Some(entries) = storage_entries else { + return; + }; + + entries.into_iter().enumerate().for_each(|(i, entry)| { + let key = FieldElement::from_str(&entry.key).expect("valid FieldElement value"); + let value = FieldElement::from_str(&entry.value).expect("valid FieldElement value"); + + println!("[{i}] key: {key:#x} value: {value:#x}", key = key, value = value); + + if self.subscribed_entities.subscribed_storage_addresses.read().contains(&key) { + self.storage.storage.write().insert(key, value); + } else { + panic!("unknown storage address"); + } + }) + } +} + +impl Future for SubscriptionClient { + type Output = (); + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let pin = self.get_mut(); + + loop { + while let Poll::Ready(Some(req)) = pin.req_rcv.poll_next_unpin(cx) { + let _ = pin.handle_event(req); + } + + match pin.sub_res_stream.poll_next_unpin(cx) { + Poll::Ready(Some(res)) => pin.handle_response(res), + + Poll::Ready(None) => return Poll::Ready(()), + Poll::Pending => return Poll::Pending, + } + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::sync::Arc; + + use dojo_types::model::EntityModel; + use dojo_types::WorldMetadata; + use parking_lot::RwLock; + use starknet::core::utils::cairo_short_string_to_felt; + use starknet::macros::{felt, short_string}; + use starknet_crypto::poseidon_hash_many; + + fn create_dummy_metadata() -> WorldMetadata { + let components = HashMap::from([( + "Position".into(), + dojo_types::model::ModelMetadata { + name: "Position".into(), + class_hash: felt!("1"), + size: 3, + }, + )]); + + WorldMetadata { components, ..Default::default() } + } + + #[test] + fn add_and_remove_subscribed_entity() { + let component_name = String::from("Position"); + let component_size: u32 = 3; + let keys = vec![felt!("0x12345")]; + + let mut expected_storage_addresses = { + let base = poseidon_hash_many(&[ + short_string!("dojo_storage"), + cairo_short_string_to_felt(&component_name).unwrap(), + poseidon_hash_many(&keys), + ]); + + (0..component_size).map(|i| base + i.into()).collect::>() + } + .into_iter(); + + let metadata = self::create_dummy_metadata(); + let entity = EntityModel { model: component_name, keys }; + + let subscribed_entities = super::SubscribedEntities::new(Arc::new(RwLock::new(metadata))); + subscribed_entities.add_entities(vec![entity.clone()]).expect("able to add entity"); + + let actual_storage_addresses_count = + subscribed_entities.subscribed_storage_addresses.read().len(); + let actual_storage_addresses = + subscribed_entities.subscribed_storage_addresses.read().clone(); + + assert!(subscribed_entities.entities.read().contains(&entity)); + assert_eq!(actual_storage_addresses_count, expected_storage_addresses.len()); + assert!(expected_storage_addresses.all(|addr| actual_storage_addresses.contains(&addr))); + + subscribed_entities.remove_entities(vec![entity.clone()]).expect("able to remove entities"); + + let actual_storage_addresses_count_after = + subscribed_entities.subscribed_storage_addresses.read().len(); + + assert_eq!(actual_storage_addresses_count_after, 0); + assert!(!subscribed_entities.entities.read().contains(&entity)); + } +} diff --git a/crates/torii/client/src/contract/component.rs b/crates/torii/client/src/contract/component.rs deleted file mode 100644 index f88f5207e9..0000000000 --- a/crates/torii/client/src/contract/component.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::vec; - -use dojo_types::component::Member; -use starknet::core::types::{BlockId, FieldElement, FunctionCall}; -use starknet::core::utils::{ - cairo_short_string_to_felt, get_selector_from_name, parse_cairo_short_string, - CairoShortStringToFeltError, ParseCairoShortStringError, -}; -use starknet::macros::short_string; -use starknet::providers::{Provider, ProviderError}; -use starknet_crypto::poseidon_hash_many; - -use crate::contract::world::{ContractReaderError, WorldContractReader}; - -#[cfg(test)] -#[path = "component_test.rs"] -mod test; - -#[derive(Debug, thiserror::Error)] -pub enum ComponentError

{ - #[error(transparent)] - ProviderError(ProviderError

), - #[error("Invalid schema length")] - InvalidSchemaLength, - #[error(transparent)] - ParseCairoShortStringError(ParseCairoShortStringError), - #[error(transparent)] - CairoShortStringToFeltError(CairoShortStringToFeltError), - #[error("Converting felt")] - ConvertingFelt, - #[error(transparent)] - ContractReaderError(ContractReaderError

), -} - -pub struct ComponentReader<'a, P: Provider + Sync> { - world: &'a WorldContractReader<'a, P>, - class_hash: FieldElement, - name: FieldElement, -} - -impl<'a, P: Provider + Sync> ComponentReader<'a, P> { - pub async fn new( - world: &'a WorldContractReader<'a, P>, - name: String, - block_id: BlockId, - ) -> Result, ComponentError> { - let name = cairo_short_string_to_felt(&name) - .map_err(ComponentError::CairoShortStringToFeltError)?; - let res = world - .provider - .call( - FunctionCall { - contract_address: world.address, - calldata: vec![name], - entry_point_selector: get_selector_from_name("component").unwrap(), - }, - block_id, - ) - .await - .map_err(ComponentError::ProviderError)?; - - Ok(Self { world, class_hash: res[0], name }) - } - - pub fn class_hash(&self) -> FieldElement { - self.class_hash - } - - pub async fn schema(&self, block_id: BlockId) -> Result, ComponentError> { - let entrypoint = get_selector_from_name("schema").unwrap(); - - let res = self - .world - .call( - "library_call", - vec![FieldElement::THREE, self.class_hash, entrypoint, FieldElement::ZERO], - block_id, - ) - .await - .map_err(ComponentError::ContractReaderError)?; - - let mut members = vec![]; - for chunk in res[3..].chunks(3) { - if chunk.len() != 3 { - return Err(ComponentError::InvalidSchemaLength); - } - - let is_key: u8 = chunk[2].try_into().map_err(|_| ComponentError::ConvertingFelt)?; - - members.push(Member { - name: parse_cairo_short_string(&chunk[0]) - .map_err(ComponentError::ParseCairoShortStringError)?, - ty: parse_cairo_short_string(&chunk[1]) - .map_err(ComponentError::ParseCairoShortStringError)?, - key: is_key == 1, - }); - } - - Ok(members) - } - - pub async fn size(&self, block_id: BlockId) -> Result> { - let entrypoint = get_selector_from_name("size").unwrap(); - - let res = self - .world - .call( - "library_call", - vec![FieldElement::THREE, self.class_hash, entrypoint, FieldElement::ZERO], - block_id, - ) - .await - .map_err(ComponentError::ContractReaderError)?; - - Ok(res[2]) - } - - pub async fn entity( - &self, - keys: Vec, - block_id: BlockId, - ) -> Result, ComponentError> { - let size: u8 = self.size(block_id).await?.try_into().unwrap(); - - let key = poseidon_hash_many(&keys); - let key = poseidon_hash_many(&[short_string!("dojo_storage"), self.name, key]); - - let mut values = vec![]; - for slot in 0..size { - let value = self - .world - .provider - .get_storage_at(self.world.address, key + slot.into(), block_id) - .await - .map_err(ComponentError::ProviderError)?; - - values.push(value); - } - - Ok(values) - } -} diff --git a/crates/torii/client/src/contract/component_test.rs b/crates/torii/client/src/contract/component_test.rs deleted file mode 100644 index 6839477ee5..0000000000 --- a/crates/torii/client/src/contract/component_test.rs +++ /dev/null @@ -1,46 +0,0 @@ -use camino::Utf8PathBuf; -use dojo_test_utils::sequencer::{ - get_default_test_starknet_config, SequencerConfig, TestSequencer, -}; -use dojo_types::component::Member; -use starknet::accounts::ConnectedAccount; -use starknet::core::types::{BlockId, BlockTag, FieldElement}; - -use crate::contract::world::test::deploy_world; -use crate::contract::world::WorldContractReader; - -#[tokio::test] -async fn test_component() { - let sequencer = - TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; - let account = sequencer.account(); - let provider = account.provider(); - let (world_address, _) = deploy_world( - &sequencer, - Utf8PathBuf::from_path_buf("../../../examples/ecs/target/dev".into()).unwrap(), - ) - .await; - - let block_id = BlockId::Tag(BlockTag::Latest); - let world = WorldContractReader::new(world_address, provider); - let component = world.component("Position", block_id).await.unwrap(); - - assert_eq!( - component.class_hash(), - FieldElement::from_hex_be( - "0x04973c97a1cd5e141d6dfe05c36517234851118ea703e510fcb72a39a092c228" - ) - .unwrap() - ); - - let members = component.schema(block_id).await.unwrap(); - - assert_eq!( - members, - vec![ - Member { name: "player".into(), ty: "ContractAddress".into(), key: true }, - Member { name: "x".into(), ty: "u32".into(), key: false }, - Member { name: "y".into(), ty: "u32".into(), key: false } - ] - ) -} diff --git a/crates/torii/client/src/contract/mod.rs b/crates/torii/client/src/contract/mod.rs index 0131b090a4..a994f340ec 100644 --- a/crates/torii/client/src/contract/mod.rs +++ b/crates/torii/client/src/contract/mod.rs @@ -1,3 +1,2 @@ -pub mod component; -pub mod system; +pub mod model; pub mod world; diff --git a/crates/torii/client/src/contract/model.rs b/crates/torii/client/src/contract/model.rs new file mode 100644 index 0000000000..c968239b7e --- /dev/null +++ b/crates/torii/client/src/contract/model.rs @@ -0,0 +1,288 @@ +use std::vec; + +use crypto_bigint::U256; +use dojo_types::model::{Enum, Member, Struct, Ty}; +use starknet::core::types::{BlockId, FieldElement, FunctionCall}; +use starknet::core::utils::{ + cairo_short_string_to_felt, get_selector_from_name, parse_cairo_short_string, + CairoShortStringToFeltError, ParseCairoShortStringError, +}; +use starknet::macros::short_string; +use starknet::providers::{Provider, ProviderError}; +use starknet_crypto::poseidon_hash_many; + +use crate::contract::world::{ContractReaderError, WorldContractReader}; + +#[cfg(test)] +#[path = "model_test.rs"] +mod model_test; + +#[derive(Debug, thiserror::Error)] +pub enum ModelError

{ + #[error(transparent)] + ProviderError(ProviderError

), + #[error("Invalid schema")] + InvalidSchema, + #[error(transparent)] + ParseCairoShortStringError(ParseCairoShortStringError), + #[error(transparent)] + CairoShortStringToFeltError(CairoShortStringToFeltError), + #[error("Converting felt")] + ConvertingFelt, + #[error("Unpacking entity")] + UnpackingEntity, + #[error(transparent)] + ContractReaderError(ContractReaderError

), +} + +pub struct ModelReader<'a, P: Provider + Sync> { + world: &'a WorldContractReader<'a, P>, + class_hash: FieldElement, + name: FieldElement, +} + +impl<'a, P: Provider + Sync> ModelReader<'a, P> { + pub async fn new( + world: &'a WorldContractReader<'a, P>, + name: String, + block_id: BlockId, + ) -> Result, ModelError> { + let name = + cairo_short_string_to_felt(&name).map_err(ModelError::CairoShortStringToFeltError)?; + let res = world + .provider + .call( + FunctionCall { + contract_address: world.address, + calldata: vec![name], + entry_point_selector: get_selector_from_name("model").unwrap(), + }, + block_id, + ) + .await + .map_err(ModelError::ProviderError)?; + + Ok(Self { world, class_hash: res[0], name }) + } + + pub fn class_hash(&self) -> FieldElement { + self.class_hash + } + + pub async fn schema(&self, block_id: BlockId) -> Result> { + let entrypoint = get_selector_from_name("schema").unwrap(); + + let res = self + .world + .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO], block_id) + .await + .map_err(ModelError::ContractReaderError)?; + + parse_ty::

(&res[1..]) + } + + pub async fn packed_size( + &self, + block_id: BlockId, + ) -> Result> { + let entrypoint = get_selector_from_name("packed_size").unwrap(); + + let res = self + .world + .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO], block_id) + .await + .map_err(ModelError::ContractReaderError)?; + + Ok(res[1]) + } + + pub async fn size(&self, block_id: BlockId) -> Result> { + let entrypoint = get_selector_from_name("size").unwrap(); + + let res = self + .world + .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO], block_id) + .await + .map_err(ModelError::ContractReaderError)?; + + Ok(res[1]) + } + + pub async fn layout( + &self, + block_id: BlockId, + ) -> Result, ModelError> { + let entrypoint = get_selector_from_name("layout").unwrap(); + + let res = self + .world + .executor_call(self.class_hash, vec![entrypoint, FieldElement::ZERO], block_id) + .await + .map_err(ModelError::ContractReaderError)?; + + Ok(res[2..].into()) + } + + pub async fn entity( + &self, + keys: Vec, + block_id: BlockId, + ) -> Result, ModelError> { + let packed_size: u8 = self.packed_size(block_id).await?.try_into().unwrap(); + let layout = self.layout(block_id).await?; + + let key = poseidon_hash_many(&keys); + let key = poseidon_hash_many(&[short_string!("dojo_storage"), self.name, key]); + + let mut packed = vec![]; + for slot in 0..packed_size { + let value = self + .world + .provider + .get_storage_at(self.world.address, key + slot.into(), block_id) + .await + .map_err(ModelError::ProviderError)?; + + packed.push(value); + } + + let unpacked = unpack::

(packed, layout.clone())?; + + Ok(unpacked) + } +} + +/// Unpacks a vector of packed values according to a given layout. +/// +/// # Arguments +/// +/// * `packed_values` - A vector of FieldElement values that are packed. +/// * `layout` - A vector of FieldElement values that describe the layout of the packed values. +/// +/// # Returns +/// +/// * `Result, ModelError>` - A Result containing a vector of unpacked +/// FieldElement values if successful, or an error if unsuccessful. +pub fn unpack( + mut packed: Vec, + layout: Vec, +) -> Result, ModelError> { + packed.reverse(); + let mut unpacked = vec![]; + + let mut unpacking: U256 = packed.pop().ok_or(ModelError::UnpackingEntity)?.as_ref().into(); + let mut offset = 0; + + // Iterate over the layout. + for size in layout { + let size: u8 = size.try_into().map_err(|_| ModelError::ConvertingFelt)?; + let size: usize = size.into(); + let remaining_bits = 251 - offset; + + // If there are less remaining bits than the size, move to the next felt for unpacking. + if remaining_bits < size { + unpacking = packed.pop().ok_or(ModelError::UnpackingEntity)?.as_ref().into(); + offset = 0; + } + + let mut mask = U256::from(0_u8); + for _ in 0..size { + mask = (mask << 1) | U256::from(1_u8); + } + + let result = mask & (unpacking >> offset); + let result_fe = FieldElement::from_hex_be(&result.to_string()) + .map_err(|_| ModelError::ConvertingFelt)?; + unpacked.push(result_fe); + + // Update unpacking to be the shifted value after extracting the result. + offset += size; + } + + Ok(unpacked) +} + +fn parse_ty(data: &[FieldElement]) -> Result> { + let member_type: u8 = data[0].try_into().unwrap(); + match member_type { + 0 => parse_simple::

(&data[1..]), + 1 => parse_struct::

(&data[1..]), + 2 => parse_enum::

(&data[1..]), + _ => Err(ModelError::InvalidSchema), + } +} + +fn parse_simple(data: &[FieldElement]) -> Result> { + let ty = parse_cairo_short_string(&data[0]).map_err(ModelError::ParseCairoShortStringError)?; + Ok(Ty::Terminal(ty)) +} + +fn parse_struct(data: &[FieldElement]) -> Result> { + let name = + parse_cairo_short_string(&data[0]).map_err(ModelError::ParseCairoShortStringError)?; + + let attrs_len: u32 = data[1].try_into().unwrap(); + let attrs_slice_start = 2; + let attrs_slice_end = attrs_slice_start + attrs_len as usize; + let _attrs = &data[attrs_slice_start..attrs_slice_end]; + + let children_len: u32 = data[attrs_slice_end].try_into().unwrap(); + let children_len = children_len as usize; + + let mut children = vec![]; + let mut offset = attrs_slice_end + 1; + + for i in 0..children_len { + let start = i + offset; + let len: u32 = data[start].try_into().unwrap(); + let slice_start = start + 1; + let slice_end = slice_start + len as usize; + children.push(parse_member::

(&data[slice_start..slice_end])?); + offset += len as usize; + } + + Ok(Ty::Struct(Struct { name, children })) +} + +fn parse_member(data: &[FieldElement]) -> Result> { + let name = + parse_cairo_short_string(&data[0]).map_err(ModelError::ParseCairoShortStringError)?; + + let attributes_len: u32 = data[1].try_into().unwrap(); + let slice_start = 2; + let slice_end = slice_start + attributes_len as usize; + let attributes = &data[slice_start..slice_end]; + + let key = attributes.contains(&cairo_short_string_to_felt("key").unwrap()); + + let ty = parse_ty::

(&data[slice_end..])?; + + Ok(Member { name, ty, key }) +} + +fn parse_enum(data: &[FieldElement]) -> Result> { + let name = + parse_cairo_short_string(&data[0]).map_err(ModelError::ParseCairoShortStringError)?; + + let attrs_len: u32 = data[1].try_into().unwrap(); + let attrs_slice_start = 2; + let attrs_slice_end = attrs_slice_start + attrs_len as usize; + let _attrs = &data[attrs_slice_start..attrs_slice_end]; + + let values_len: u32 = data[attrs_slice_end].try_into().unwrap(); + let values_len = values_len as usize; + + let mut values = vec![]; + let mut offset = attrs_slice_end + 1; + + for i in 0..values_len { + let start = i + offset; + let len: u32 = data[start].try_into().unwrap(); + let slice_start = start + 1; + let slice_end = slice_start + len as usize; + values.push(parse_ty::

(&data[slice_start..slice_end])?); + offset += len as usize; + } + + Ok(Ty::Enum(Enum { name, values })) +} diff --git a/crates/torii/client/src/contract/model_test.rs b/crates/torii/client/src/contract/model_test.rs new file mode 100644 index 0000000000..8bdf412051 --- /dev/null +++ b/crates/torii/client/src/contract/model_test.rs @@ -0,0 +1,105 @@ +use camino::Utf8PathBuf; +use dojo_test_utils::sequencer::{ + get_default_test_starknet_config, SequencerConfig, TestSequencer, +}; +use dojo_types::model::{Enum, Member, Struct, Ty}; +use starknet::accounts::ConnectedAccount; +use starknet::core::types::{BlockId, BlockTag, FieldElement}; + +use crate::contract::world::test::deploy_world; +use crate::contract::world::WorldContractReader; + +#[tokio::test(flavor = "multi_thread")] +async fn test_model() { + let sequencer = + TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; + let account = sequencer.account(); + let provider = account.provider(); + let (world_address, _) = deploy_world( + &sequencer, + Utf8PathBuf::from_path_buf("../../../examples/ecs/target/dev".into()).unwrap(), + ) + .await; + + let block_id = BlockId::Tag(BlockTag::Latest); + let world = WorldContractReader::new(world_address, provider); + let position = world.model("Position", block_id).await.unwrap(); + let schema = position.schema(block_id).await.unwrap(); + + assert_eq!( + schema, + Ty::Struct(Struct { + name: "Position".to_string(), + children: vec![ + Member { + name: "player".to_string(), + ty: Ty::Terminal("ContractAddress".to_string()), + key: true + }, + Member { + name: "vec".to_string(), + ty: Ty::Struct(Struct { + name: "Vec2".to_string(), + children: vec![ + Member { + name: "x".to_string(), + ty: Ty::Terminal("u32".to_string()), + key: false + }, + Member { + name: "y".to_string(), + ty: Ty::Terminal("u32".to_string()), + key: false + } + ] + }), + key: false + } + ] + }) + ); + + assert_eq!( + position.class_hash(), + FieldElement::from_hex_be( + "0x07a812f2cfb414d5aa04bb9a3c91cdcaf1d30e193bd6cb7faf9b7c294722fab4" + ) + .unwrap() + ); + + let moves = world.model("Moves", block_id).await.unwrap(); + let schema = moves.schema(block_id).await.unwrap(); + + assert_eq!( + schema, + Ty::Struct(Struct { + name: "Moves".to_string(), + children: vec![ + Member { + name: "player".to_string(), + ty: Ty::Terminal("ContractAddress".to_string()), + key: true + }, + Member { + name: "remaining".to_string(), + ty: Ty::Terminal("u8".to_string()), + key: false + }, + Member { + name: "last_direction".to_string(), + ty: Ty::Enum(Enum { + name: "Direction".to_string(), + values: vec![ + Ty::Terminal("None".to_string()), + Ty::Terminal("Left".to_string()), + Ty::Terminal("Right".to_string()), + Ty::Terminal("Up".to_string()), + Ty::Terminal("Down".to_string()) + ] + }), + key: false + } + ] + }) + ); +} diff --git a/crates/torii/client/src/contract/system.rs b/crates/torii/client/src/contract/system.rs deleted file mode 100644 index 9717beb8a5..0000000000 --- a/crates/torii/client/src/contract/system.rs +++ /dev/null @@ -1,179 +0,0 @@ -use dojo_types::system::Dependency; -use starknet::accounts::ConnectedAccount; -use starknet::core::types::{BlockId, FieldElement, FunctionCall, InvokeTransactionResult}; -use starknet::core::utils::{ - cairo_short_string_to_felt, get_selector_from_name, parse_cairo_short_string, - CairoShortStringToFeltError, ParseCairoShortStringError, -}; -use starknet::providers::{Provider, ProviderError}; - -use crate::contract::world::{ - ContractReaderError, WorldContract, WorldContractError, WorldContractReader, -}; - -#[cfg(test)] -#[path = "system_test.rs"] -mod test; - -#[derive(Debug, thiserror::Error)] -pub enum SystemError { - #[error(transparent)] - WorldError(WorldContractError), - #[error(transparent)] - ProviderError(ProviderError

), - #[error(transparent)] - ParseCairoShortStringError(ParseCairoShortStringError), - #[error(transparent)] - CairoShortStringToFeltError(CairoShortStringToFeltError), - #[error(transparent)] - ContractReaderError(ContractReaderError

), - #[error(transparent)] - ReaderError(SystemReaderError

), -} - -pub struct System<'a, A: ConnectedAccount + Sync> { - world: &'a WorldContract<'a, A>, - reader: SystemReader<'a, A::Provider>, - name: String, -} - -impl<'a, A: ConnectedAccount + Sync> System<'a, A> { - pub async fn new( - world: &'a WorldContract<'a, A>, - name: String, - block_id: BlockId, - ) -> Result, SystemError::Error>> { - Ok(Self { - name: name.clone(), - world, - reader: SystemReader::new(&world.reader, name, block_id) - .await - .map_err(SystemError::ReaderError)?, - }) - } - - pub fn class_hash(&self) -> FieldElement { - self.reader.class_hash - } - - pub async fn call( - &self, - calldata: Vec, - block_id: BlockId, - ) -> Result, SystemError::Error>> - { - self.reader.call(calldata, block_id).await.map_err(SystemError::ReaderError) - } - - pub async fn execute( - &self, - calldata: Vec, - ) -> Result::Error>> - { - let res = - self.world.execute(&self.name, calldata).await.map_err(SystemError::WorldError)?; - - Ok(res) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum SystemReaderError

{ - #[error(transparent)] - ProviderError(ProviderError

), - #[error(transparent)] - ParseCairoShortStringError(ParseCairoShortStringError), - #[error(transparent)] - CairoShortStringToFeltError(CairoShortStringToFeltError), - #[error(transparent)] - ContractReaderError(ContractReaderError

), - #[error("Invalid dependency length")] - InvalidDependencyLength, -} - -pub struct SystemReader<'a, P: Provider + Sync> { - world: &'a WorldContractReader<'a, P>, - name: String, - class_hash: FieldElement, -} - -impl<'a, P: Provider + Sync> SystemReader<'a, P> { - pub async fn new( - world: &'a WorldContractReader<'a, P>, - name: String, - block_id: BlockId, - ) -> Result, SystemReaderError> { - let res = world - .provider - .call( - FunctionCall { - contract_address: world.address, - calldata: vec![ - cairo_short_string_to_felt(&name) - .map_err(SystemReaderError::CairoShortStringToFeltError)?, - ], - entry_point_selector: get_selector_from_name("system").unwrap(), - }, - block_id, - ) - .await - .map_err(SystemReaderError::ProviderError)?; - - Ok(Self { name, world, class_hash: res[0] }) - } - - pub fn class_hash(&self) -> FieldElement { - self.class_hash - } - - pub async fn call( - &self, - mut calldata: Vec, - block_id: BlockId, - ) -> Result, SystemReaderError> { - calldata.insert(0, (calldata.len() as u64).into()); - - let res = self - .world - .call(&self.name, calldata, block_id) - .await - .map_err(SystemReaderError::ContractReaderError)?; - - Ok(res) - } - - pub async fn dependencies( - &self, - block_id: BlockId, - ) -> Result, SystemReaderError> { - let entrypoint = get_selector_from_name("dependencies").unwrap(); - - let res = self - .world - .call( - "library_call", - vec![FieldElement::THREE, self.class_hash, entrypoint, FieldElement::ZERO], - block_id, - ) - .await - .map_err(SystemReaderError::ContractReaderError)?; - - let mut dependencies = vec![]; - for chunk in res[3..].chunks(2) { - if chunk.len() != 2 { - return Err(SystemReaderError::InvalidDependencyLength); - } - - let is_write: bool = chunk[1] == FieldElement::ONE; - - dependencies.push(Dependency { - name: parse_cairo_short_string(&chunk[0]) - .map_err(SystemReaderError::ParseCairoShortStringError)?, - read: !is_write, - write: is_write, - }); - } - - Ok(dependencies) - } -} diff --git a/crates/torii/client/src/contract/system_test.rs b/crates/torii/client/src/contract/system_test.rs deleted file mode 100644 index 9f3bb249d1..0000000000 --- a/crates/torii/client/src/contract/system_test.rs +++ /dev/null @@ -1,48 +0,0 @@ -use camino::Utf8PathBuf; -use dojo_test_utils::sequencer::{ - get_default_test_starknet_config, SequencerConfig, TestSequencer, -}; -use starknet::accounts::Account; -use starknet::core::types::{BlockId, BlockTag}; -use starknet_crypto::FieldElement; - -use crate::contract::world::test::deploy_world; -use crate::contract::world::WorldContract; - -#[tokio::test] -async fn test_system() { - let sequencer = - TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; - let account = sequencer.account(); - let (world_address, _) = deploy_world( - &sequencer, - Utf8PathBuf::from_path_buf("../../../examples/ecs/target/dev".into()).unwrap(), - ) - .await; - - let block_id: BlockId = BlockId::Tag(BlockTag::Latest); - let world = WorldContract::new(world_address, &account); - let spawn = world.system("spawn", block_id).await.unwrap(); - - let _ = spawn.execute(vec![]).await.unwrap(); - - let component = world.component("Moves", block_id).await.unwrap(); - let moves = component.entity(vec![account.address()], block_id).await.unwrap(); - - assert_eq!(moves, vec![10_u8.into()]); - - let move_system = world.system("move", block_id).await.unwrap(); - - let _ = move_system.execute(vec![FieldElement::ONE]).await.unwrap(); - let _ = move_system.execute(vec![FieldElement::THREE]).await.unwrap(); - - let moves = component.entity(vec![account.address()], block_id).await.unwrap(); - - assert_eq!(moves, vec![8_u8.into()]); - - let position_component = world.component("Position", block_id).await.unwrap(); - - let position = position_component.entity(vec![account.address()], block_id).await.unwrap(); - - assert_eq!(position, vec![11_u8.into(), 11_u8.into()]); -} diff --git a/crates/torii/client/src/contract/world.rs b/crates/torii/client/src/contract/world.rs index d13d945c3b..135b3618ac 100644 --- a/crates/torii/client/src/contract/world.rs +++ b/crates/torii/client/src/contract/world.rs @@ -7,8 +7,7 @@ use starknet::core::utils::{ }; use starknet::providers::{Provider, ProviderError}; -use crate::contract::component::{ComponentError, ComponentReader}; -use crate::contract::system::{System, SystemError, SystemReader, SystemReaderError}; +use crate::contract::model::{ModelError, ModelReader}; #[cfg(test)] #[path = "world_test.rs"] @@ -55,20 +54,20 @@ impl<'a, A: ConnectedAccount + Sync> WorldContract<'a, A> { pub async fn grant_writer( &self, - component: &str, + model: &str, system: &str, ) -> Result< InvokeTransactionResult, WorldContractError::Error>, > { - let component = cairo_short_string_to_felt(component) + let model = cairo_short_string_to_felt(model) .map_err(WorldContractError::CairoShortStringToFeltError)?; let system = cairo_short_string_to_felt(system) .map_err(WorldContractError::CairoShortStringToFeltError)?; self.account .execute(vec![Call { - calldata: vec![component, system], + calldata: vec![model, system], to: self.address, selector: get_selector_from_name("grant_writer").unwrap(), }]) @@ -77,22 +76,16 @@ impl<'a, A: ConnectedAccount + Sync> WorldContract<'a, A> { .map_err(WorldContractError::AccountError) } - pub async fn register_components( + pub async fn register_models( &self, - components: &[FieldElement], + models: &[FieldElement], ) -> Result::Error>> { - let calls = components + let calls = models .iter() .map(|c| Call { to: self.address, - // function selector: "register_component" - selector: FieldElement::from_mont([ - 11981012454229264524, - 8784065169116922201, - 15056747385353365869, - 456849768949735353, - ]), + selector: get_selector_from_name("register_model").unwrap(), calldata: vec![*c], }) .collect::>(); @@ -100,54 +93,6 @@ impl<'a, A: ConnectedAccount + Sync> WorldContract<'a, A> { self.account.execute(calls).send().await } - pub async fn register_systems( - &self, - systems: &[FieldElement], - ) -> Result::Error>> - { - let calls = systems - .iter() - .map(|s| Call { - to: self.address, - // function selector: "register_system" - selector: FieldElement::from_mont([ - 6581716859078500959, - 16871126355047595269, - 14219012428168968926, - 473332093618875024, - ]), - calldata: vec![*s], - }) - .collect::>(); - - self.account.execute(calls).send().await - } - - pub async fn execute( - &self, - name: &str, - mut calldata: Vec, - ) -> Result< - InvokeTransactionResult, - WorldContractError::Error>, - > { - calldata.insert(0, (calldata.len() as u64).into()); - calldata.insert( - 0, - cairo_short_string_to_felt(name) - .map_err(WorldContractError::CairoShortStringToFeltError)?, - ); - self.account - .execute(vec![Call { - calldata, - to: self.address, - selector: get_selector_from_name("execute").unwrap(), - }]) - .send() - .await - .map_err(WorldContractError::AccountError) - } - pub async fn executor( &self, block_id: BlockId, @@ -164,21 +109,12 @@ impl<'a, A: ConnectedAccount + Sync> WorldContract<'a, A> { self.reader.call(system, calldata, block_id).await } - pub async fn component( - &'a self, - name: &str, - block_id: BlockId, - ) -> Result, ComponentError<::Error>> - { - self.reader.component(name, block_id).await - } - - pub async fn system( + pub async fn model( &'a self, name: &str, block_id: BlockId, - ) -> Result, SystemError::Error>> { - System::new(self, name.to_string(), block_id).await + ) -> Result, ModelError<::Error>> { + self.reader.model(name, block_id).await } } @@ -204,7 +140,7 @@ impl<'a, P: Provider + Sync> WorldContractReader<'a, P> { pub async fn is_authorized( &self, system: &str, - component: &str, + model: &str, execution_role: &str, block_id: BlockId, ) -> Result> { @@ -216,7 +152,7 @@ impl<'a, P: Provider + Sync> WorldContractReader<'a, P> { calldata: vec![ cairo_short_string_to_felt(system) .map_err(ContractReaderError::CairoShortStringToFeltError)?, - cairo_short_string_to_felt(component) + cairo_short_string_to_felt(model) .map_err(ContractReaderError::CairoShortStringToFeltError)?, cairo_short_string_to_felt(execution_role) .map_err(ContractReaderError::CairoShortStringToFeltError)?, @@ -271,6 +207,27 @@ impl<'a, P: Provider + Sync> WorldContractReader<'a, P> { Ok(res[0]) } + pub async fn executor_call( + &self, + class_hash: FieldElement, + mut calldata: Vec, + block_id: BlockId, + ) -> Result, ContractReaderError> { + calldata.insert(0, class_hash); + + self.provider + .call( + FunctionCall { + contract_address: self.executor(block_id).await.unwrap(), + calldata, + entry_point_selector: get_selector_from_name("call").unwrap(), + }, + block_id, + ) + .await + .map_err(ContractReaderError::ProviderError) + } + pub async fn call( &self, system: &str, @@ -295,19 +252,11 @@ impl<'a, P: Provider + Sync> WorldContractReader<'a, P> { .map_err(ContractReaderError::ProviderError) } - pub async fn component( - &'a self, - name: &str, - block_id: BlockId, - ) -> Result, ComponentError> { - ComponentReader::new(self, name.to_string(), block_id).await - } - - pub async fn system( + pub async fn model( &'a self, name: &str, block_id: BlockId, - ) -> Result, SystemReaderError> { - SystemReader::new(self, name.to_string(), block_id).await + ) -> Result, ModelError> { + ModelReader::new(self, name.to_string(), block_id).await } } diff --git a/crates/torii/client/src/contract/world_test.rs b/crates/torii/client/src/contract/world_test.rs index 1b7d5ae73a..450efaffc0 100644 --- a/crates/torii/client/src/contract/world_test.rs +++ b/crates/torii/client/src/contract/world_test.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use camino::Utf8PathBuf; use dojo_test_utils::sequencer::{ get_default_test_starknet_config, SequencerConfig, TestSequencer, @@ -11,7 +13,7 @@ use starknet::core::types::{BlockId, BlockTag, FieldElement}; use super::{WorldContract, WorldContractReader}; -#[tokio::test] +#[tokio::test(flavor = "multi_thread")] async fn test_world_contract_reader() { let sequencer = TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()).await; @@ -47,40 +49,54 @@ pub async fn deploy_world( let executor_address = strategy .executor .unwrap() - .deploy(manifest.clone().executor.class_hash, vec![], &account) + .deploy(manifest.clone().executor.class_hash, vec![], &account, Default::default()) .await .unwrap() .contract_address; + + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; + let world_address = strategy .world .unwrap() - .deploy(manifest.clone().world.class_hash, vec![executor_address], &account) + .deploy( + manifest.clone().world.class_hash, + vec![executor_address], + &account, + Default::default(), + ) .await .unwrap() .contract_address; let mut declare_output = vec![]; - for component in strategy.components { - let res = component.declare(&account).await.unwrap(); + for model in strategy.models { + let res = model.declare(&account, Default::default()).await.unwrap(); declare_output.push(res); } + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; + let _ = WorldContract::new(world_address, &account) - .register_components(&declare_output.iter().map(|o| o.class_hash).collect::>()) + .register_models(&declare_output.iter().map(|o| o.class_hash).collect::>()) .await .unwrap(); - let mut declare_output = vec![]; - for system in strategy.systems { - let res = system.declare(&account).await.unwrap(); - declare_output.push(res); + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; + + for contract in strategy.contracts { + let declare_res = contract.declare(&account, Default::default()).await.unwrap(); + contract + .deploy(declare_res.class_hash, vec![], &account, Default::default()) + .await + .unwrap(); } - let world = WorldContract::new(world_address, &account); - let _ = world - .register_systems(&declare_output.iter().map(|o| o.class_hash).collect::>()) - .await - .unwrap(); + // wait for the tx to be mined + tokio::time::sleep(Duration::from_millis(250)).await; (world_address, executor_address) } diff --git a/crates/torii/client/src/lib.rs b/crates/torii/client/src/lib.rs index 43cf496b95..3e2de193b9 100644 --- a/crates/torii/client/src/lib.rs +++ b/crates/torii/client/src/lib.rs @@ -1,4 +1,7 @@ +#[cfg(target_arch = "wasm32")] +extern crate wasm_prost as prost; +#[cfg(target_arch = "wasm32")] +extern crate wasm_tonic as tonic; + +pub mod client; pub mod contract; -pub mod provider; -pub mod storage; -pub mod sync; diff --git a/crates/torii/client/src/provider/jsonrpc.rs b/crates/torii/client/src/provider/jsonrpc.rs deleted file mode 100644 index 67ccdab552..0000000000 --- a/crates/torii/client/src/provider/jsonrpc.rs +++ /dev/null @@ -1,83 +0,0 @@ -use async_trait::async_trait; -use starknet::core::types::{BlockId, BlockTag}; -use starknet::providers::jsonrpc::{JsonRpcClientError, JsonRpcTransport}; -use starknet::providers::JsonRpcClient; -use starknet_crypto::FieldElement; - -use super::Provider; -use crate::contract::component::ComponentError; -use crate::contract::system::SystemReaderError; -use crate::contract::world::WorldContractReader; - -#[derive(Debug, thiserror::Error)] -pub enum JsonRpcProviderError

{ - #[error(transparent)] - SystemReader(SystemReaderError

), - #[error(transparent)] - ComponetReader(ComponentError

), -} - -/// An implementation of [Provider] which uses a Starknet [JsonRpcClient] to query the World. -pub struct JsonRpcProvider { - /// Starknet JSON-RPC client. - client: JsonRpcClient, - /// The address of the World contract. - world_address: FieldElement, - /// The block id to query the World with. - block_id: BlockId, -} - -impl JsonRpcProvider -where - T: JsonRpcTransport + Sync + Send, -{ - pub fn new(client: JsonRpcClient, world_address: FieldElement) -> Self { - Self { client, world_address, block_id: BlockId::Tag(BlockTag::Latest) } - } - - fn world(&self) -> WorldContractReader<'_, JsonRpcClient> { - WorldContractReader::new(self.world_address, &self.client) - } -} - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl Provider for JsonRpcProvider -where - T: JsonRpcTransport + Sync + Send, -{ - type Error = JsonRpcProviderError>; - - async fn system(&self, name: &str) -> Result { - let world = self.world(); - let class_hash = world - .system(name, self.block_id) - .await - .map_err(JsonRpcProviderError::SystemReader)? - .class_hash(); - Ok(class_hash) - } - - async fn component(&self, name: &str) -> Result { - let world = self.world(); - let class_hash = world - .component(name, self.block_id) - .await - .map_err(JsonRpcProviderError::ComponetReader)? - .class_hash(); - Ok(class_hash) - } - - async fn entity( - &self, - component: &str, - keys: Vec, - ) -> Result, Self::Error> { - let world = self.world(); - let component = world - .component(component, self.block_id) - .await - .map_err(JsonRpcProviderError::ComponetReader)?; - component.entity(keys, self.block_id).await.map_err(JsonRpcProviderError::ComponetReader) - } -} diff --git a/crates/torii/client/src/provider/mod.rs b/crates/torii/client/src/provider/mod.rs deleted file mode 100644 index 26fd8eab12..0000000000 --- a/crates/torii/client/src/provider/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::error::Error; - -use async_trait::async_trait; -use starknet_crypto::FieldElement; - -pub mod jsonrpc; - -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -pub trait Provider { - type Error: Error + Send + Sync; - - /// Get the class hash of a component. - async fn component(&self, name: &str) -> Result; - - /// Get the class hash of a system. - async fn system(&self, name: &str) -> Result; - - /// Get the component values of an entity. - async fn entity( - &self, - component: &str, - keys: Vec, - ) -> Result, Self::Error>; -} diff --git a/crates/torii/client/src/storage.rs b/crates/torii/client/src/storage.rs deleted file mode 100644 index b26393b87f..0000000000 --- a/crates/torii/client/src/storage.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::error::Error; - -use async_trait::async_trait; -use starknet::macros::short_string; -use starknet_crypto::{poseidon_hash_many, FieldElement}; - -// TODO: is this low level enough? -/// Low level storage interface -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -pub trait EntityStorage { - type Error: Error + Send + Sync; - - /// This function mimic `world::set_entity` of `dojo-core` - async fn set( - &mut self, - component: FieldElement, - keys: Vec, - values: Vec, - ) -> Result<(), Self::Error>; - - /// This function mimic `world::entity` of `dojo-core` - async fn get( - &self, - component: FieldElement, - keys: Vec, - length: usize, - ) -> Result, Self::Error>; -} - -pub fn component_storage_base_address( - component: FieldElement, - keys: &[FieldElement], -) -> FieldElement { - let id = poseidon_hash_many(keys); - poseidon_hash_many(&[short_string!("dojo_storage"), component, id]) -} diff --git a/crates/torii/client/src/sync.rs b/crates/torii/client/src/sync.rs deleted file mode 100644 index ed6e42eda2..0000000000 --- a/crates/torii/client/src/sync.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::collections::HashSet; -use std::sync::Arc; - -#[cfg(target_arch = "wasm32")] -use async_std::sync::RwLock as AsyncRwLock; -use parking_lot::RwLock; -use starknet::core::utils::cairo_short_string_to_felt; -use starknet_crypto::FieldElement; -use thiserror::Error; -#[cfg(not(target_arch = "wasm32"))] -use tokio::{sync::RwLock as AsyncRwLock, time::Duration}; -#[cfg(target_arch = "wasm32")] -use web_sys::WorkerGlobalScope; - -use crate::provider::Provider; -use crate::storage::EntityStorage; - -#[derive(Debug, Error)] -pub enum ClientError { - #[error(transparent)] - Provider(P), - #[error(transparent)] - Storage(S), -} - -/// Request to sync a component of an entity. -#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct Entity { - /// Component name - pub component: String, - /// The entity keys - pub keys: Vec, -} - -/// A client to sync entity components from the World. -pub struct Client { - // We wrap it in an Arc to allow sharing the storage between threads. - /// Storage to store the synced entity component values. - storage: Arc>, - /// A provider implementation to query the World for entity components. - provider: P, - /// The entity components to sync. - pub sync_entities: RwLock>, - - /// The interval to run the syncing loop. - // DEV: It's wrapped in [parking_lot::RwLock] to prevent from having the `Self::start()` to be - // `mut`. Having it on `mut` seems to be causing this issue https://github.com/rustwasm/wasm-bindgen/issues/1578 when compiled to wasm32. - #[cfg(not(target_arch = "wasm32"))] - interval: AsyncRwLock, - #[cfg(target_arch = "wasm32")] - interval: i32, -} - -impl Client -where - S: EntityStorage + Send + Sync, - P: Provider + Send + Sync + 'static, -{ - #[cfg(not(target_arch = "wasm32"))] - const DEFAULT_INTERVAL: Duration = Duration::from_secs(1); - - #[cfg(target_arch = "wasm32")] - const DEFAULT_INTERVAL: i32 = 1000; // 1 second - - pub fn new(storage: Arc>, provider: P, entities: Vec) -> Client { - Self { - storage, - provider, - sync_entities: RwLock::new(HashSet::from_iter(entities)), - #[cfg(not(target_arch = "wasm32"))] - interval: AsyncRwLock::new(tokio::time::interval_at( - tokio::time::Instant::now() + Self::DEFAULT_INTERVAL, - Self::DEFAULT_INTERVAL, - )), - #[cfg(target_arch = "wasm32")] - interval: Self::DEFAULT_INTERVAL, - } - } - - pub fn with_interval( - mut self, - #[cfg(not(target_arch = "wasm32"))] milisecond: u64, - #[cfg(target_arch = "wasm32")] milisecond: i32, - ) -> Self { - #[cfg(not(target_arch = "wasm32"))] - { - use tokio::time::{interval_at, Instant}; - let interval = Duration::from_millis(milisecond); - self.interval = AsyncRwLock::new(interval_at(Instant::now() + interval, interval)); - } - - #[cfg(target_arch = "wasm32")] - { - self.interval = milisecond; - } - - self - } - - /// Returns the storage instance used by the client. - pub fn storage(&self) -> Arc> { - self.storage.clone() - } - - /// Starts the syncing process. - /// This function will run forever. - pub async fn start(&self) -> Result<(), ClientError> { - loop { - #[cfg(not(target_arch = "wasm32"))] - self.interval.write().await.tick().await; - - #[cfg(target_arch = "wasm32")] - sleep(self.interval).await; - - let entities = self.sync_entities.read().clone(); - for entity in entities { - let values = self - .provider - .entity(&entity.component, entity.keys.clone()) - .await - .map_err(ClientError::Provider)?; - - #[cfg(not(target_arch = "wasm32"))] - self.storage - .write() - .await - .set( - cairo_short_string_to_felt(&entity.component).unwrap(), - entity.keys, - values, - ) - .await - .map_err(ClientError::Storage)?; - - #[cfg(target_arch = "wasm32")] - self.storage - .write() - .await - .set( - cairo_short_string_to_felt(&entity.component).unwrap(), - entity.keys, - values, - ) - .await - .map_err(ClientError::Storage)?; - } - } - } -} - -#[cfg(target_arch = "wasm32")] -async fn sleep(delay: i32) { - use wasm_bindgen::JsCast; - let mut cb = |resolve: js_sys::Function, _reject: js_sys::Function| { - // if we are in a worker, use the global worker's scope - // otherwise, use the window's scope - if let Ok(worker_scope) = js_sys::global().dyn_into::() { - worker_scope - .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, delay) - .expect("should register `setTimeout`"); - } else { - web_sys::window() - .unwrap() - .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, delay) - .expect("should register `setTimeout`"); - } - }; - let p = js_sys::Promise::new(&mut cb); - wasm_bindgen_futures::JsFuture::from(p).await.unwrap(); -} diff --git a/crates/torii/client/wasm/.cargo/config.toml b/crates/torii/client/wasm/.cargo/config.toml index f4e8c002fc..85c748284e 100644 --- a/crates/torii/client/wasm/.cargo/config.toml +++ b/crates/torii/client/wasm/.cargo/config.toml @@ -1,2 +1,5 @@ [build] target = "wasm32-unknown-unknown" + +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/crates/torii/client/wasm/Cargo.lock b/crates/torii/client/wasm/Cargo.lock index 7e0efd4ec0..8e455f5432 100644 --- a/crates/torii/client/wasm/Cargo.lock +++ b/crates/torii/client/wasm/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -28,6 +28,43 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -43,6 +80,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "ark-ff" version = "0.4.2" @@ -55,7 +98,7 @@ dependencies = [ "ark-std", "derivative", "digest", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -153,6 +196,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "async-trait" version = "0.1.73" @@ -161,7 +226,16 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", +] + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", ] [[package]] @@ -182,11 +256,56 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -205,9 +324,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bigdecimal" @@ -227,6 +346,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bitvec" version = "1.0.1" @@ -250,9 +375,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byte-slice-cast" @@ -268,15 +393,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] @@ -289,15 +414,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "winapi", + "windows-targets", ] [[package]] @@ -312,9 +437,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] @@ -344,6 +469,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crc32fast" version = "1.3.2" @@ -353,6 +493,40 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.16" @@ -370,11 +544,12 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" dependencies = [ "generic-array", + "rand_core", "subtle", "zeroize", ] @@ -419,7 +594,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] @@ -430,14 +605,14 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] name = "deranged" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" dependencies = [ "serde", ] @@ -468,21 +643,34 @@ dependencies = [ name = "dojo-types" version = "0.2.1" dependencies = [ + "hex", "serde", "starknet", + "strum", + "strum_macros", + "thiserror", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -493,6 +681,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "eth-keystore" version = "0.5.0" @@ -512,7 +721,7 @@ dependencies = [ "sha2", "sha3", "thiserror", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -548,6 +757,18 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "fixed-hash" version = "0.8.0" @@ -560,6 +781,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.27" @@ -570,6 +797,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -591,6 +830,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.28" @@ -598,6 +852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -606,6 +861,28 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + [[package]] name = "futures-io" version = "0.3.28" @@ -620,7 +897,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] @@ -641,9 +918,13 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -674,15 +955,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -708,6 +989,58 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.0", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.4", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -724,6 +1057,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -746,6 +1088,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.8.0" @@ -791,9 +1139,21 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls", + "rustls 0.21.7", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", "tokio", - "tokio-rustls", + "tokio-io-timeout", ] [[package]] @@ -903,6 +1263,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -918,6 +1287,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -942,11 +1320,34 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -964,11 +1365,26 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "memoffset" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] [[package]] name = "mime" @@ -976,6 +1392,22 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -996,11 +1428,45 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "version_check", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -1026,11 +1492,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1043,9 +1519,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parity-scale-codec" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" dependencies = [ "arrayvec", "bitvec", @@ -1057,9 +1533,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1067,6 +1543,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1074,7 +1561,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -1085,7 +1586,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets", ] @@ -1111,24 +1612,80 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.0", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" -version = "0.2.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.37", +] + [[package]] name = "primitive-types" version = "0.12.1" @@ -1178,13 +1735,121 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +dependencies = [ + "bytes", + "prost-derive 0.12.1", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools 0.10.5", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease 0.1.25", + "prost 0.11.9", + "prost-types 0.11.9", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-build" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac" +dependencies = [ + "bytes", + "heck", + "itertools 0.11.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease 0.2.15", + "prost 0.12.1", + "prost-types 0.12.1", + "regex", + "syn 2.0.37", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + +[[package]] +name = "prost-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +dependencies = [ + "prost 0.12.1", +] + [[package]] name = "quote" version = "1.0.33" @@ -1230,22 +1895,80 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed02d09394c94ffbdfdc755ad62a132e94c3224a8354e78a1200ced34df12edf" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", ] +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -1262,19 +1985,19 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.21.7", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.2", "winreg", ] @@ -1297,7 +2020,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -1334,11 +2057,36 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "rustls" -version = "0.21.6" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", @@ -1352,19 +2100,25 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", ] [[package]] name = "rustls-webpki" -version = "0.101.3" +version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ "ring", "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.15" @@ -1380,6 +2134,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1410,24 +2170,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde-wasm-bindgen" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78" dependencies = [ "js-sys", "serde", @@ -1436,20 +2196,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -1504,14 +2264,25 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] -name = "sha2" -version = "0.10.7" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -1528,20 +2299,29 @@ dependencies = [ "keccak", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "socket2" @@ -1555,9 +2335,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys", @@ -1569,16 +2349,128 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools 0.11.0", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +dependencies = [ + "ahash 0.7.6", + "atoi", + "bitflags 1.3.2", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "indexmap 1.9.3", + "itoa", + "libc", + "libsqlite3-sys", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.20.9", + "rustls-pemfile", + "serde", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "uuid 1.4.1", + "webpki-roots 0.22.6", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn 1.0.109", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls 0.23.4", +] + [[package]] name = "starknet" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcb61961b91757a9bc2d11549067445b2f921bd957f53710db35449767a1ba3" +checksum = "6f0623b045f3dc10aef030c9ddd4781cff9cbe1188b71063cc510b75d1f96be6" dependencies = [ "starknet-accounts", "starknet-contract", "starknet-core", - "starknet-crypto 0.6.0", + "starknet-crypto", "starknet-ff", "starknet-macros", "starknet-providers", @@ -1587,11 +2479,12 @@ dependencies = [ [[package]] name = "starknet-accounts" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111ed887e4db14f0df1f909905e7737e4730770c8ed70997b58a71d5d940daac" +checksum = "68e97edc480348dca300e5a8234e6c4e6f2f1ac028f2b16fcce294ebe93d07f4" dependencies = [ "async-trait", + "auto_impl", "starknet-core", "starknet-providers", "starknet-signers", @@ -1600,9 +2493,9 @@ dependencies = [ [[package]] name = "starknet-contract" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d6f81a647694b2cb669ab60e77954b57bf5fbc757f5fcaf0a791c3bd341f04" +checksum = "69b86e3f6b3ca9a5c45271ab10871c99f7dc82fee3199d9f8c7baa2a1829947d" dependencies = [ "serde", "serde_json", @@ -1615,11 +2508,11 @@ dependencies = [ [[package]] name = "starknet-core" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91f89c79b641618de8aa9668d74c6b6634659ceca311c6318a35c025f9d4d969" +checksum = "b796a32a7400f7d85e95d3900b5cee7a392b2adbf7ad16093ed45ec6f8d85de6" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "flate2", "hex", "serde", @@ -1627,28 +2520,8 @@ dependencies = [ "serde_json_pythonic", "serde_with", "sha3", - "starknet-crypto 0.6.0", - "starknet-ff", -] - -[[package]] -name = "starknet-crypto" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693e6362f150f9276e429a910481fb7f3bcb8d6aa643743f587cfece0b374874" -dependencies = [ - "crypto-bigint", - "hex", - "hmac", - "num-bigint", - "num-integer", - "num-traits", - "rfc6979", - "sha2", - "starknet-crypto-codegen", - "starknet-curve 0.3.0", + "starknet-crypto", "starknet-ff", - "zeroize", ] [[package]] @@ -1666,7 +2539,7 @@ dependencies = [ "rfc6979", "sha2", "starknet-crypto-codegen", - "starknet-curve 0.4.0", + "starknet-curve", "starknet-ff", "zeroize", ] @@ -1677,18 +2550,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" dependencies = [ - "starknet-curve 0.4.0", - "starknet-ff", - "syn 2.0.29", -] - -[[package]] -name = "starknet-curve" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252610baff59e4c4332ce3569f7469c5d3f9b415a2240d698fb238b2b4fc0942" -dependencies = [ + "starknet-curve", "starknet-ff", + "syn 2.0.37", ] [[package]] @@ -1717,19 +2581,19 @@ dependencies = [ [[package]] name = "starknet-macros" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a5865ee0ed22ade86bdf45e7c09c5641f1c59ccae12c21ecde535b2b6bf64a" +checksum = "ef846b6bb48fc8c3e9a2aa9b5b037414f04a908d9db56493a3ae69a857eb2506" dependencies = [ "starknet-core", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] name = "starknet-providers" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbfccb46a8969fb3ac803718d9d8270cff4eed5b7f6b9ba234875ad2cc997c5" +checksum = "c3b136c26b72ff1756f0844e0aa80bab680ceb99d63921826facbb8e7340ff82" dependencies = [ "async-trait", "auto_impl", @@ -1747,9 +2611,9 @@ dependencies = [ [[package]] name = "starknet-signers" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313524cc79344015ef2a8618947332ab17012b5c50600c7f84c60989bdec980" +checksum = "d9386015d2e6dc3df285bfb33a3afd8ad7596c70ed38ab57019de4d2dfc7826f" dependencies = [ "async-trait", "auto_impl", @@ -1757,7 +2621,7 @@ dependencies = [ "eth-keystore", "rand", "starknet-core", - "starknet-crypto 0.6.0", + "starknet-crypto", "thiserror", ] @@ -1767,12 +2631,42 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.37", +] + [[package]] name = "subtle" version = "2.5.0" @@ -1792,46 +2686,65 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 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 = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] name = "time" -version = "0.3.25" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", "itoa", @@ -1842,15 +2755,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1889,26 +2802,85 @@ dependencies = [ "bytes", "libc", "mio", + "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", - "socket2 0.5.3", + "signal-hook-registry", + "socket2 0.5.4", + "tokio-macros", "windows-sys", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.9", + "tokio", + "webpki", +] + [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.7", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", "tokio", + "tungstenite", ] [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -1926,29 +2898,159 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +dependencies = [ + "async-trait", + "base64 0.21.4", + "bytes", + "flate2", + "futures-core", + "futures-util", + "http", + "http-body", + "percent-encoding", + "pin-project", + "prost 0.11.9", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c00bc15e49625f3d2f20b17082601e5e17cf27ead69e805174026c194b6664" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.4", + "bytes", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.12.1", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +dependencies = [ + "prettyplease 0.1.25", + "proc-macro2", + "prost-build 0.11.9", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tonic-build" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d37bb15da06ae9bb945963066baca6561b505af93a52e949a85d28558459a2" +dependencies = [ + "prettyplease 0.2.15", + "proc-macro2", + "prost-build 0.12.1", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "tonic-web" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2953fe95664e86519e0d1c4bdd65007d93bc47a59c9af512280977aa9e46b871" +dependencies = [ + "base64 0.21.4", + "bytes", + "http", + "http-body", + "hyper", + "pin-project", + "tokio-stream", + "tonic 0.10.1", + "tower-http", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-web-wasm-client" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5987e92915a51a4b05e69a0ef903a7b76f16674f7ee66534f87fd3323e2d3a" +dependencies = [ + "base64 0.21.4", + "byteorder", + "bytes", + "futures-util", + "http", + "http-body", + "httparse", + "js-sys", + "pin-project", + "thiserror", + "tonic 0.9.2", + "tower-service", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + [[package]] name = "torii-client" version = "0.2.1" dependencies = [ - "async-std", + "anyhow", "async-trait", + "crypto-bigint", "dojo-types", + "futures", + "futures-util", "js-sys", - "parking_lot", + "parking_lot 0.12.1", + "prost 0.11.9", + "prost 0.12.1", "serde", + "serde_json", "starknet", - "starknet-crypto 0.5.1", + "starknet-crypto", "thiserror", "tokio", + "tonic 0.10.1", + "tonic 0.9.2", + "torii-grpc", + "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1961,19 +3063,101 @@ dependencies = [ "async-std", "async-trait", "console_error_panic_hook", + "dojo-types", + "futures", "js-sys", - "parking_lot", + "parking_lot 0.12.1", "serde", "serde-wasm-bindgen", + "serde_json", "starknet", "thiserror", + "tokio", "torii-client", + "torii-grpc", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-bindgen-test", "web-sys", ] +[[package]] +name = "torii-grpc" +version = "0.2.1" +dependencies = [ + "anyhow", + "bytes", + "dojo-types", + "futures", + "futures-util", + "hyper", + "parking_lot 0.12.1", + "prost 0.11.9", + "prost 0.12.1", + "rayon", + "sqlx", + "starknet", + "starknet-crypto", + "thiserror", + "tokio", + "tokio-stream", + "tonic 0.10.1", + "tonic 0.9.2", + "tonic-build 0.10.1", + "tonic-build 0.9.2", + "tonic-web", + "tonic-web-wasm-client", + "tower", + "tracing", + "url", + "warp", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.0", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -1987,10 +3171,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "tracing-core" version = "0.1.31" @@ -2006,11 +3203,30 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uint" @@ -2024,6 +3240,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -2032,9 +3257,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2045,6 +3270,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "untrusted" version = "0.7.1" @@ -2053,15 +3290,21 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "0.8.2" @@ -2072,6 +3315,18 @@ dependencies = [ "serde", ] +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2087,6 +3342,37 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2114,7 +3400,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", "wasm-bindgen-shared", ] @@ -2148,7 +3434,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2159,6 +3445,43 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.64" @@ -2171,9 +3494,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", @@ -2188,6 +3511,24 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[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 = "winapi" version = "0.3.9" @@ -2287,20 +3628,21 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys", ] [[package]] @@ -2329,5 +3671,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", ] diff --git a/crates/torii/client/wasm/Cargo.toml b/crates/torii/client/wasm/Cargo.toml index 9ed334a067..f0573acd38 100644 --- a/crates/torii/client/wasm/Cargo.toml +++ b/crates/torii/client/wasm/Cargo.toml @@ -13,16 +13,20 @@ crate-type = [ "cdylib", "rlib" ] [dependencies] async-std = { version = "1.12.0", default-features = false, features = [ "std" ] } async-trait = "0.1.68" +dojo-types = { path = "../../../dojo-types" } parking_lot = "0.12.1" serde = { version = "1.0.156", features = [ "derive" ] } -starknet = "0.5.0" +serde_json = "1.0.64" +starknet = "0.6.0" thiserror = "1.0.32" +tokio = { version = "1.32.0", default-features = false, features = [ "rt" ] } torii-client = { path = ".." } +torii-grpc = { path = "../../grpc", features = [ "client" ] } url = "2.4.0" -# wasm +# WASM js-sys = "0.3.64" -serde-wasm-bindgen = "0.5.0" +serde-wasm-bindgen = "0.6.0" wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4.37" web-sys = { version = "0.3.4", features = [ 'MessageEvent', 'Window', 'Worker', 'WorkerGlobalScope', 'console' ] } @@ -31,3 +35,7 @@ web-sys = { version = "0.3.4", features = [ 'MessageEvent', 'Window', 'Worker', # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = "0.1.7" +futures = "0.3.28" + +[dev-dependencies] +wasm-bindgen-test = "0.3.0" diff --git a/crates/torii/client/wasm/README.md b/crates/torii/client/wasm/README.md index 86fa42de7a..a5e31df73c 100644 --- a/crates/torii/client/wasm/README.md +++ b/crates/torii/client/wasm/README.md @@ -1,12 +1,12 @@ This crate includes a basic usage for the WASM module. -1. Run the build script to compile the crate to WASM +1. Run the build script to compile the crate to WASM make sure [wasm pack](https://rustwasm.github.io/wasm-pack/installer) is installed ```sh ./build.sh ``` -2. Start the HTTP server (make sure `simple-http-server` is installed) +2. Start the HTTP server (make sure [`simple-http-server`](https://github.com/TheWaWaR/simple-http-server) is installed) ```sh simple-http-server . diff --git a/crates/torii/client/wasm/index.html b/crates/torii/client/wasm/index.html index 437478216b..85de016375 100644 --- a/crates/torii/client/wasm/index.html +++ b/crates/torii/client/wasm/index.html @@ -5,16 +5,8 @@ -

-

Main Thread/Wasm Web Worker Interaction

- - - -
-
- - + diff --git a/crates/torii/client/wasm/index.js b/crates/torii/client/wasm/index.js index 608a143158..af79a2b9ff 100644 --- a/crates/torii/client/wasm/index.js +++ b/crates/torii/client/wasm/index.js @@ -1,8 +1,3 @@ -// We only need `startup` here which is the main entry point -// In theory, we could also use all other functions/struct types from Rust which we have bound with -// `#[wasm_bindgen]` -const { setup } = wasm_bindgen; - async function run_wasm() { // Load the wasm file by awaiting the Promise returned by `wasm_bindgen` // `wasm_bindgen` was imported in `index.html` @@ -10,16 +5,16 @@ async function run_wasm() { console.log("index.js loaded"); - const syncWorker = new Worker("./worker.js"); + const clientWorker = new Worker("./worker.js"); - syncWorker.onmessage = function (e) { + clientWorker.onmessage = function (e) { const event = e.data.type; const data = e.data.data; - if (event === "getComponentValue") { + if (event === "getModelValue") { console.log( - "Main thread | component: ", - data.component, + "Main thread | model: ", + data.model, "keys: ", data.keys, "values: ", @@ -31,30 +26,18 @@ async function run_wasm() { }; setTimeout(() => { - // Add the entity to sync - syncWorker.postMessage({ - type: "addEntityToSync", - data: { - component: "Position", - keys: [ - "0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0", - ], - }, - }); - setInterval(() => { // Get the entity values from the sync worker - syncWorker.postMessage({ - type: "getComponentValue", + clientWorker.postMessage({ + type: "getModelValue", data: { - component: "Position", + model: "Position", keys: [ - "0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0", + "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973", ], - length: 2, }, }); - }, 1000); + }, 2000); }, 1000); } diff --git a/crates/torii/client/wasm/rust-toolchain.toml b/crates/torii/client/wasm/rust-toolchain.toml new file mode 100644 index 0000000000..5d56faf9ae --- /dev/null +++ b/crates/torii/client/wasm/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/crates/torii/client/wasm/src/lib.rs b/crates/torii/client/wasm/src/lib.rs index 6a021ff038..bbc4f251f5 100644 --- a/crates/torii/client/wasm/src/lib.rs +++ b/crates/torii/client/wasm/src/lib.rs @@ -1,92 +1,88 @@ -use std::sync::Arc; +use std::str::FromStr; -use async_std::sync::RwLock as AsyncRwLock; use starknet::core::types::FieldElement; -use starknet::core::utils::cairo_short_string_to_felt; -use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::JsonRpcClient; -use torii_client::provider::jsonrpc::JsonRpcProvider; -use torii_client::storage::EntityStorage; -use torii_client::sync::{self, Client, Entity}; -use url::Url; use wasm_bindgen::prelude::*; -mod storage; +type JsFieldElement = JsValue; +type JsEntityComponent = JsValue; -use storage::InMemoryStorage; - -/// A type wrapper to expose the client to WASM. #[wasm_bindgen] -pub struct WasmClient(Client>); +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} #[wasm_bindgen] -impl WasmClient { - /// Create an instance of the client. This will create an instance of the client - /// without any entity components to sync. - /// - /// # Arguments - /// * `url` - The url of the Starknet JSON-RPC provider. - /// * `world_address` - The address of the World contract to sync with. - #[wasm_bindgen(constructor)] - pub fn new(url: &str, world_address: &str) -> Self { - let world_address = FieldElement::from_hex_be(world_address).unwrap(); - - let storage = Arc::new(AsyncRwLock::new(InMemoryStorage::new())); - let provider = JsonRpcProvider::new( - JsonRpcClient::new(HttpTransport::new(Url::parse(url).unwrap())), - world_address, - ); - - Self(sync::Client::new(storage, provider, vec![])) - } - - /// Start the syncing loop. - pub async fn start(&self) -> Result<(), JsValue> { - console_error_panic_hook::set_once(); - self.0.start().await.map_err(|e| JsValue::from_str(&e.to_string())) - } +pub struct Client(torii_client::client::Client); - /// Returns the component values of the requested entity keys. - #[wasm_bindgen(js_name = getComponentValue)] - pub async fn get_component_value( +#[wasm_bindgen] +impl Client { + /// Returns the model values of the requested entity. + #[wasm_bindgen(js_name = getModelValue)] + pub async fn get_model_value( &self, - component: &str, - keys: JsValue, - length: usize, - ) -> Result { + model: &str, + keys: Vec, + ) -> Result, JsValue> { console_error_panic_hook::set_once(); - let keys = serde_wasm_bindgen::from_value::>(keys)?; - let values = self - .0 - .storage() - .read() - .await - .get( - cairo_short_string_to_felt(component) - .map_err(|e| JsValue::from_str(&e.to_string()))?, - keys, - length, - ) - .await - .map_err(|e| JsValue::from_str(&e.to_string()))?; + let keys = keys + .into_iter() + .map(serde_wasm_bindgen::from_value::) + .collect::, _>>() + .map_err(|err| { + JsValue::from_str(format!("failed to parse entity keys: {err}").as_str()) + })?; - Ok(serde_wasm_bindgen::to_value(&values)?) + match self.0.entity(model.to_string(), keys) { + Some(values) => Ok(Some(serde_wasm_bindgen::to_value(&values)?)), + None => Ok(None), + } } /// Add a new entity to be synced by the client. #[wasm_bindgen(js_name = addEntityToSync)] - pub fn add_entity_to_sync(&self, entity: JsValue) -> Result<(), JsValue> { + pub fn add_entities_to_sync(&self, entities: Vec) -> Result<(), JsValue> { console_error_panic_hook::set_once(); - let entity = serde_wasm_bindgen::from_value::(entity)?; - self.0.sync_entities.write().insert(entity); - Ok(()) + let _entities = entities + .into_iter() + .map(serde_wasm_bindgen::from_value::) + .collect::, _>>()?; + unimplemented!("add_entity_to_sync"); } /// Returns the list of entities that are currently being synced. #[wasm_bindgen(getter, js_name = syncedEntities)] pub fn synced_entities(&self) -> Result { console_error_panic_hook::set_once(); - Ok(serde_wasm_bindgen::to_value(&self.0.sync_entities.read().iter().collect::>())?) + let entities = self.0.synced_entities(); + serde_wasm_bindgen::to_value(&entities).map_err(|e| e.into()) } } + +#[wasm_bindgen] +pub async fn spawn_client( + torii_url: &str, + rpc_url: &str, + world_address: &str, + initial_entities_to_sync: Vec, +) -> Result { + console_error_panic_hook::set_once(); + + let entities = initial_entities_to_sync + .into_iter() + .map(serde_wasm_bindgen::from_value::) + .collect::, _>>()?; + + let world_address = FieldElement::from_str(world_address).map_err(|err| { + JsValue::from_str(format!("failed to parse World address: {err}").as_str()) + })?; + + let client = torii_client::client::ClientBuilder::new() + .set_entities_to_sync(entities) + .build(torii_url.into(), rpc_url.into(), world_address) + .await + .map_err(|err| JsValue::from_str(format!("failed to build client: {err}").as_str()))?; + + Ok(Client(client)) +} diff --git a/crates/torii/client/wasm/src/storage.rs b/crates/torii/client/wasm/src/storage.rs deleted file mode 100644 index 999c70c27d..0000000000 --- a/crates/torii/client/wasm/src/storage.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::collections::HashMap; - -use async_trait::async_trait; -use serde::{Deserialize, Serialize}; -use starknet::core::types::FieldElement; -use torii_client::storage::{component_storage_base_address, EntityStorage}; - -/// Simple in memory implementation of [EntityStorage] -#[derive(Serialize, Deserialize)] -pub struct InMemoryStorage { - /// storage key -> Component value - pub inner: HashMap, -} - -impl InMemoryStorage { - pub fn new() -> Self { - Self { inner: HashMap::new() } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum InMemoryStorageError {} - -// Example implementation of [EntityStorage] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -impl EntityStorage for InMemoryStorage { - type Error = InMemoryStorageError; - - async fn set( - &mut self, - component: FieldElement, - keys: Vec, - values: Vec, - ) -> Result<(), Self::Error> { - let base_address = component_storage_base_address(component, &keys); - for (offset, value) in values.into_iter().enumerate() { - self.inner.insert(base_address + offset.into(), value); - } - Ok(()) - } - - async fn get( - &self, - component: FieldElement, - keys: Vec, - length: usize, - ) -> Result, Self::Error> { - let base_address = component_storage_base_address(component, &keys); - let mut values = Vec::with_capacity(length); - for i in 0..length { - let address = base_address + i.into(); - let value = self.inner.get(&address).cloned(); - values.push(value.unwrap_or(FieldElement::ZERO)); - } - Ok(values) - } -} diff --git a/crates/torii/client/wasm/worker.js b/crates/torii/client/wasm/worker.js index b3b7612b2d..737cd8f34f 100644 --- a/crates/torii/client/wasm/worker.js +++ b/crates/torii/client/wasm/worker.js @@ -1,60 +1,70 @@ // The worker has its own scope and no direct access to functions/objects of the // global scope. We import the generated JS file to make `wasm_bindgen` // available which we need to initialize our Wasm code. -importScripts("./pkg/dojo_client_wasm.js"); +importScripts("./pkg/torii_client_wasm.js"); -console.log("Initializing worker"); +console.log("Initializing client worker..."); // In the worker, we have a different struct that we want to use as in // `index.js`. -const { WasmClient } = wasm_bindgen; +const { spawn_client } = wasm_bindgen; async function setup() { // Load the wasm file by awaiting the Promise returned by `wasm_bindgen`. - await wasm_bindgen("./pkg/dojo_client_wasm_bg.wasm"); + await wasm_bindgen("./pkg/torii_client_wasm_bg.wasm"); - const client = new WasmClient( - "http://localhost:5050", - "0xa89fbc16c54a1042db8e877e27ba1924417336a1ad2fd1bb495bb909b4829e" - ); + try { + const client = await spawn_client( + "http://localhost:8080/grpc", + "http://localhost:5050", + "0x2430f23de0cd9a957e1beb7aa8ef2db2af872cc7bb3058b9be833111d5518f5", + [ + { + model: "Position", + keys: [ + "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973", + ], + }, + ] + ); - client.start(); + // setup the message handler for the worker + self.onmessage = function (e) { + const event = e.data.type; + const data = e.data.data; - // setup the message handler for the worker - self.onmessage = function (e) { - const event = e.data.type; - const data = e.data.data; - - if (event === "getComponentValue") { - getComponentValueHandler(client, data); - } else if (event === "addEntityToSync") { - addEntityToSyncHandler(client, data); - } else { - console.log("Sync Worker: Unknown event type", event); - } - }; + if (event === "getModelValue") { + getModelValueHandler(client, data); + } else if (event === "addEntityToSync") { + addEntityToSyncHandler(client, data); + } else { + console.log("Sync Worker: Unknown event type", event); + } + }; + } catch (e) { + console.error("error spawning client: ", e); + } } -function addEntityToSyncHandler(client, data) { - console.log("Sync Worker | Adding new entity to sync | data: ", data); - client.addEntityToSync(data); -} +// function addEntityToSyncHandler(client, data) { +// console.log("Sync Worker | Adding new entity to sync | data: ", data); +// client.addEntityToSync(data); +// } /// Handler for the `get_entity` event from the main thread. /// Returns back the entity data to the main thread via `postMessage`. -async function getComponentValueHandler(client, data) { - console.log("Sync Worker | Getting component value | data: ", data); +async function getModelValueHandler(client, data) { + console.log("Sync Worker | Getting model value | data: ", data); - const component = data.component; + const model = data.model; const keys = data.keys; - const length = data.length; - const values = await client.getComponentValue(component, keys, length); + const values = await client.getModelValue(model, keys); self.postMessage({ - type: "getComponentValue", + type: "getModelValue", data: { - component: "Position", + model: "Position", keys, values, }, diff --git a/crates/torii/client/wasm/yarn.lock b/crates/torii/client/wasm/yarn.lock index 173206106d..c77ea551c9 100644 --- a/crates/torii/client/wasm/yarn.lock +++ b/crates/torii/client/wasm/yarn.lock @@ -103,7 +103,7 @@ dependencies: chalk "^2.4.1" command-exists "^1.2.7" - watchpack "^2.1.1" + watchpack "^2.2.0" which "^2.0.2" "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": @@ -303,8 +303,8 @@ ansi-html-community@0.0.8: integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + version "2.2.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.2.0.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^4.1.0: @@ -330,7 +330,7 @@ anymatch@^2.0.0: integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== dependencies: micromatch "^3.1.4" - normalize-path "^2.1.1" + normalize-path "^2.2.0" arr-diff@^4.0.0: version "4.0.0" @@ -794,7 +794,7 @@ debug@^3.2.7: resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: - ms "^2.1.1" + ms "^2.2.0" debug@^4.1.0, debug@^4.1.1: version "4.3.4" @@ -1700,9 +1700,9 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" +is-extglob@^2.1.0, is-extglob@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.2.0.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^2.0.0: @@ -1722,7 +1722,7 @@ is-glob@^4.0.0: resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: - is-extglob "^2.1.1" + is-extglob "^2.2.0" is-number@^3.0.0: version "3.0.0" @@ -1951,7 +1951,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.27, mime-types@~2.2.07, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -2010,7 +2010,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.2.0: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -2083,9 +2083,9 @@ node-releases@^2.0.13: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" +normalize-path@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.2.0.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== dependencies: remove-trailing-separator "^1.0.1" @@ -2103,8 +2103,8 @@ npm-run-path@^2.0.0: path-key "^2.0.0" nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + version "2.2.0" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.2.0.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== dependencies: boolbase "^1.0.0" @@ -2413,7 +2413,7 @@ qs@^6.11.0: dependencies: side-channel "^1.0.4" -querystringify@^2.1.1: +querystringify@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== @@ -2526,9 +2526,9 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" +require-directory@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.2.0.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-main-filename@^2.0.0: @@ -2694,7 +2694,7 @@ serve-index@^1.9.1: debug "2.6.9" escape-html "~1.0.3" http-errors "~1.6.2" - mime-types "~2.1.17" + mime-types "~2.2.07" parseurl "~1.3.2" serve-static@1.15.0: @@ -2778,8 +2778,8 @@ signal-exit@^3.0.0: integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + version "2.2.0" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.2.0.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== dependencies: define-property "^1.0.0" @@ -2984,7 +2984,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: +tapable@^2.0.0, tapable@^2.2.0, tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== @@ -3028,8 +3028,8 @@ to-object-path@^0.3.0: kind-of "^3.0.2" to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + version "2.2.0" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.2.0.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== dependencies: is-number "^3.0.0" @@ -3116,7 +3116,7 @@ url-parse@^1.5.10: resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: - querystringify "^2.1.1" + querystringify "^2.2.0" requires-port "^1.0.0" url@^0.11.0: @@ -3162,7 +3162,7 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -watchpack@^2.1.1, watchpack@^2.4.0: +watchpack@^2.2.0, watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== @@ -3291,7 +3291,7 @@ webpack@^5.49.0: mime-types "^2.1.27" neo-async "^2.6.2" schema-utils "^3.2.0" - tapable "^2.1.1" + tapable "^2.2.0" terser-webpack-plugin "^5.3.7" watchpack "^2.4.0" webpack-sources "^3.2.3" @@ -3376,7 +3376,7 @@ yargs@^13.3.2: cliui "^5.0.0" find-up "^3.0.0" get-caller-file "^2.0.1" - require-directory "^2.1.1" + require-directory "^2.2.0" require-main-filename "^2.0.0" set-blocking "^2.0.0" string-width "^3.0.0" diff --git a/crates/torii/core/Cargo.toml b/crates/torii/core/Cargo.toml index a74964ea44..12591f12f5 100644 --- a/crates/torii/core/Cargo.toml +++ b/crates/torii/core/Cargo.toml @@ -10,19 +10,27 @@ version.workspace = true [dependencies] anyhow.workspace = true +async-stream = "0.3.0" async-trait.workspace = true chrono.workspace = true dojo-types = { path = "../../dojo-types" } dojo-world = { path = "../../dojo-world" } +futures-channel = "0.3.0" +futures-util = "0.3.0" +hex = "0.4.3" +lazy_static = "1.4.0" log = "0.4.17" +once_cell.workspace = true serde.workspace = true serde_json.workspace = true +slab = "0.4.2" sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] } starknet-crypto.workspace = true starknet.workspace = true -tokio = { version = "1.20.1", features = [ "full" ] } +tokio = { version = "1.32.0", features = [ "sync" ], default-features = true } tokio-stream = "0.1.11" tokio-util = "0.7.7" +torii-client = { path = "../client" } tracing.workspace = true [dev-dependencies] diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs new file mode 100644 index 0000000000..5207b8b130 --- /dev/null +++ b/crates/torii/core/src/engine.rs @@ -0,0 +1,249 @@ +use std::error::Error; +use std::time::Duration; + +use starknet::core::types::{ + BlockId, BlockWithTxs, Event, InvokeTransaction, InvokeTransactionReceipt, + MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, Transaction, TransactionReceipt, +}; +use starknet::core::utils::get_selector_from_name; +use starknet::providers::Provider; +use tokio::sync::mpsc::Sender as BoundedSender; +use tokio::time::sleep; +use tokio_util::sync::CancellationToken; +use torii_client::contract::world::WorldContractReader; +use tracing::{error, info, warn}; + +use crate::processors::{BlockProcessor, EventProcessor, TransactionProcessor}; +use crate::sql::{Executable, Sql}; + +pub struct Processors { + pub block: Vec>>, + pub transaction: Vec>>, + pub event: Vec>>, +} + +impl Default for Processors

{ + fn default() -> Self { + Self { block: vec![], event: vec![], transaction: vec![] } + } +} + +#[derive(Debug)] +pub struct EngineConfig { + pub block_time: Duration, + pub start_block: u64, +} + +impl Default for EngineConfig { + fn default() -> Self { + Self { block_time: Duration::from_secs(1), start_block: 0 } + } +} + +pub struct Engine<'a, P: Provider + Sync> +where + P::Error: 'static, +{ + world: &'a WorldContractReader<'a, P>, + db: &'a Sql, + provider: &'a P, + processors: Processors

, + config: EngineConfig, + block_sender: Option>, +} + +impl<'a, P: Provider + Sync> Engine<'a, P> +where + P::Error: 'static, +{ + pub fn new( + world: &'a WorldContractReader<'a, P>, + db: &'a Sql, + provider: &'a P, + processors: Processors

, + config: EngineConfig, + block_sender: Option>, + ) -> Self { + Self { world, db, provider, processors, config, block_sender } + } + + pub async fn start(&self, cts: CancellationToken) -> Result<(), Box> { + if self.db.head().await? == 0 { + self.db.set_head(self.config.start_block).await?; + } else if self.config.start_block != 0 { + warn!("start block ignored, stored head exists and will be used instead"); + } + + loop { + if cts.is_cancelled() { + break Ok(()); + } + + let head = self.db.head().await?; + match self.sync_to_head(head).await { + Ok(block_with_txs) => block_with_txs, + Err(e) => { + error!("getting block: {}", e); + continue; + } + }; + + sleep(self.config.block_time).await; + } + } + + pub async fn sync_to_head(&self, from: u64) -> Result> { + let latest_block_number = self.provider.block_hash_and_number().await?.block_number; + + if from < latest_block_number { + // if `from` == 0, then the block may or may not be processed yet. + let from = if from == 0 { from } else { from + 1 }; + self.sync_range(from, latest_block_number).await?; + }; + + Ok(latest_block_number) + } + + pub async fn sync_range(&self, mut from: u64, to: u64) -> Result<(), Box> { + // Process all blocks from current to latest. + while from <= to { + let block_with_txs = match self.provider.get_block_with_txs(BlockId::Number(from)).await + { + Ok(block_with_txs) => block_with_txs, + Err(e) => { + error!("getting block: {}", e); + continue; + } + }; + + // send the current block number + if let Some(ref block_sender) = self.block_sender { + block_sender.send(from).await.expect("failed to send block number to gRPC server"); + } + + self.process(block_with_txs).await?; + + self.db.set_head(from).await?; + self.db.execute().await?; + from += 1; + } + + Ok(()) + } + + async fn process(&self, block: MaybePendingBlockWithTxs) -> Result<(), Box> { + let block: BlockWithTxs = match block { + MaybePendingBlockWithTxs::Block(block) => block, + _ => return Ok(()), + }; + + process_block(self.db, self.provider, &self.processors.block, &block).await?; + + for transaction in block.clone().transactions { + let invoke_transaction = match &transaction { + Transaction::Invoke(invoke_transaction) => invoke_transaction, + _ => continue, + }; + + let invoke_transaction = match invoke_transaction { + InvokeTransaction::V1(invoke_transaction) => invoke_transaction, + _ => continue, + }; + + let receipt = match self + .provider + .get_transaction_receipt(invoke_transaction.transaction_hash) + .await + { + Ok(receipt) => receipt, + _ => continue, + }; + + let receipt = match receipt { + MaybePendingTransactionReceipt::Receipt(receipt) => receipt, + _ => continue, + }; + + if let TransactionReceipt::Invoke(invoke_receipt) = receipt.clone() { + for (event_idx, event) in invoke_receipt.events.iter().enumerate() { + if event.from_address != self.world.address { + continue; + } + + process_event( + self.world, + self.db, + self.provider, + &self.processors.event, + &block, + &invoke_receipt, + event, + event_idx, + ) + .await?; + } + } + + process_transaction( + self.db, + self.provider, + &self.processors.transaction, + &block, + &receipt.clone(), + ) + .await?; + } + + info!("processed block: {}", block.block_number); + + Ok(()) + } +} + +async fn process_block( + db: &Sql, + provider: &P, + processors: &[Box>], + block: &BlockWithTxs, +) -> Result<(), Box> { + for processor in processors { + processor.process(db, provider, block).await?; + } + Ok(()) +} + +async fn process_transaction( + db: &Sql, + provider: &P, + processors: &[Box>], + block: &BlockWithTxs, + receipt: &TransactionReceipt, +) -> Result<(), Box> { + for processor in processors { + processor.process(db, provider, block, receipt).await? + } + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +async fn process_event( + world: &WorldContractReader<'_, P>, + db: &Sql, + provider: &P, + processors: &[Box>], + block: &BlockWithTxs, + invoke_receipt: &InvokeTransactionReceipt, + event: &Event, + event_idx: usize, +) -> Result<(), Box> { + db.store_event(event, event_idx, invoke_receipt.transaction_hash).await?; + + for processor in processors { + if get_selector_from_name(&processor.event_key())? == event.keys[0] { + processor.process(world, db, provider, block, invoke_receipt, event).await?; + } + } + + Ok(()) +} diff --git a/crates/torii/core/src/lib.rs b/crates/torii/core/src/lib.rs index 097225d35c..668937bca8 100644 --- a/crates/torii/core/src/lib.rs +++ b/crates/torii/core/src/lib.rs @@ -1,14 +1,11 @@ -use anyhow::Result; -use async_trait::async_trait; -use dojo_world::manifest::{Component, Manifest, System}; use serde::Deserialize; use sqlx::FromRow; -use starknet::core::types::FieldElement; use crate::types::SQLFieldElement; -// pub mod memory; +pub mod engine; pub mod processors; +pub mod simple_broker; pub mod sql; pub mod types; @@ -23,23 +20,3 @@ pub struct World { #[sqlx(try_from = "String")] executor_class_hash: SQLFieldElement, } - -#[async_trait] -pub trait State { - async fn load_from_manifest(&self, manifest: Manifest) -> Result<()>; - async fn head(&self) -> Result; - async fn set_head(&self, head: u64) -> Result<()>; - async fn world(&self) -> Result; - async fn set_world(&self, world: World) -> Result<()>; - async fn register_component(&self, component: Component) -> Result<()>; - async fn register_system(&self, system: System) -> Result<()>; - async fn set_entity( - &self, - component: String, - keys: Vec, - values: Vec, - ) -> Result<()>; - async fn delete_entity(&self, component: String, key: FieldElement) -> Result<()>; - async fn entity(&self, component: String, key: FieldElement) -> Result>; - async fn entities(&self, component: String) -> Result>>; -} diff --git a/crates/torii/core/src/memory.rs b/crates/torii/core/src/memory.rs deleted file mode 100644 index d874cf1e3f..0000000000 --- a/crates/torii/core/src/memory.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::collections::HashMap; - -use anyhow::Result; -use async_trait::async_trait; -use dojo_world::manifest::{Component, Manifest, System}; -use starknet::core::types::FieldElement; - -use super::State; - -type Partition = FieldElement; -type Key = FieldElement; -type Entities = HashMap>>; - -#[derive(Default)] -pub struct InMemory { - head: u64, - components: Vec, - systems: Vec, - components_to_entites: HashMap, -} - -#[async_trait] -impl State for InMemory { - async fn load_from_manifest(&mut self, _manifest: Manifest) -> Result<()> { - Ok(()) - } - - async fn head(&self) -> Result { - Ok(self.head) - } - - async fn set_head(&mut self, head: u64) -> Result<()> { - self.head = head; - Ok(()) - } - - async fn register_component(&mut self, component: Component) -> Result<()> { - self.components.push(component); - Ok(()) - } - - async fn register_system(&mut self, system: System) -> Result<()> { - self.systems.push(system); - Ok(()) - } - - async fn set_entity( - &mut self, - component: String, - key: FieldElement, - values: HashMap, - ) -> Result<()> { - // if let Some(component_data) = self.components_to_entites.get_mut(&component) { - // let partition_data = component_data.entry(partition).or_insert_with(HashMap::new); - // partition_data.insert(key, values); - // } - Ok(()) - } - - async fn delete_entity(&mut self, component: String, key: FieldElement) -> Result<()> { - if let Some(component_data) = self.components_to_entites.get_mut(&component) { - if let Some(partition_data) = component_data.get_mut(&partition) { - partition_data.remove(&key); - } - } - Ok(()) - } - - async fn entity(&self, component: String, key: FieldElement) -> Result> { - if let Some(component_data) = self.components_to_entites.get(&component) { - if let Some(partition_data) = component_data.get(&partition) { - if let Some(entity) = partition_data.get(&key) { - return Ok(entity.clone()); - } - } - } - Ok(vec![]) - } - - async fn entities(&self, component: String) -> Result>> { - let mut result = Vec::new(); - if let Some(component_data) = self.components_to_entites.get(&component) { - if let Some(partition_data) = component_data.get(&partition) { - for entity in partition_data.values() { - result.push(entity.clone()); - } - } - } - Ok(result) - } -} diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index 993c670d22..71cf21d5ff 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -1,45 +1,43 @@ use anyhow::{Error, Result}; use async_trait::async_trait; -use starknet::core::types::{BlockWithTxs, Event, TransactionReceipt}; -use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcTransport}; +use starknet::core::types::{BlockWithTxs, Event, InvokeTransactionReceipt, TransactionReceipt}; +use starknet::providers::Provider; +use torii_client::contract::world::WorldContractReader; -use crate::State; +use crate::sql::Sql; -pub mod register_component; +pub mod register_model; pub mod register_system; pub mod store_set_record; +// pub mod store_system_call; #[async_trait] -pub trait EventProcessor { +pub trait EventProcessor { fn event_key(&self) -> String; async fn process( &self, - storage: &S, - provider: &JsonRpcClient, + world: &WorldContractReader<'_, P>, + storage: &Sql, + provider: &P, block: &BlockWithTxs, - transaction_receipt: &TransactionReceipt, + invoke_receipt: &InvokeTransactionReceipt, event: &Event, ) -> Result<(), Error>; } #[async_trait] -pub trait BlockProcessor { +pub trait BlockProcessor { fn get_block_number(&self) -> String; - async fn process( - &self, - storage: &S, - provider: &JsonRpcClient, - block: &BlockWithTxs, - ) -> Result<(), Error>; + async fn process(&self, storage: &Sql, provider: &P, block: &BlockWithTxs) + -> Result<(), Error>; } #[async_trait] -pub trait TransactionProcessor { - fn get_transaction_hash(&self) -> String; +pub trait TransactionProcessor { async fn process( &self, - storage: &S, - provider: &JsonRpcClient, + storage: &Sql, + provider: &P, block: &BlockWithTxs, transaction_receipt: &TransactionReceipt, ) -> Result<(), Error>; diff --git a/crates/torii/core/src/processors/register_component.rs b/crates/torii/core/src/processors/register_component.rs deleted file mode 100644 index c5b0c6bf94..0000000000 --- a/crates/torii/core/src/processors/register_component.rs +++ /dev/null @@ -1,38 +0,0 @@ -use anyhow::{Error, Ok, Result}; -use async_trait::async_trait; -use dojo_world::manifest::Component; -use starknet::core::types::{BlockWithTxs, Event, TransactionReceipt}; -use starknet::core::utils::parse_cairo_short_string; -use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcTransport}; -use tracing::info; - -use super::EventProcessor; -use crate::State; - -#[derive(Default)] -pub struct RegisterComponentProcessor; - -#[async_trait] -impl EventProcessor for RegisterComponentProcessor { - fn event_key(&self) -> String { - "ComponentRegistered".to_string() - } - - async fn process( - &self, - storage: &S, - _provider: &JsonRpcClient, - _block: &BlockWithTxs, - _transaction_receipt: &TransactionReceipt, - event: &Event, - ) -> Result<(), Error> { - let name = parse_cairo_short_string(&event.data[0])?; - - info!("registered component: {}", name); - - storage - .register_component(Component { name, class_hash: event.data[1], ..Default::default() }) - .await?; - Ok(()) - } -} diff --git a/crates/torii/core/src/processors/register_model.rs b/crates/torii/core/src/processors/register_model.rs new file mode 100644 index 0000000000..e97a8bdd0e --- /dev/null +++ b/crates/torii/core/src/processors/register_model.rs @@ -0,0 +1,40 @@ +use anyhow::{Error, Ok, Result}; +use async_trait::async_trait; +use starknet::core::types::{BlockId, BlockTag, BlockWithTxs, Event, InvokeTransactionReceipt}; +use starknet::core::utils::parse_cairo_short_string; +use starknet::providers::Provider; +use torii_client::contract::world::WorldContractReader; +use tracing::info; + +use super::EventProcessor; +use crate::sql::Sql; + +#[derive(Default)] +pub struct RegisterModelProcessor; + +#[async_trait] +impl EventProcessor

for RegisterModelProcessor { + fn event_key(&self) -> String { + "ModelRegistered".to_string() + } + + async fn process( + &self, + world: &WorldContractReader<'_, P>, + db: &Sql, + _provider: &P, + _block: &BlockWithTxs, + _invoke_receipt: &InvokeTransactionReceipt, + event: &Event, + ) -> Result<(), Error> { + let name = parse_cairo_short_string(&event.data[0])?; + let model = world.model(&name, BlockId::Tag(BlockTag::Latest)).await?; + let schema = model.schema(BlockId::Tag(BlockTag::Latest)).await?; + let layout = model.layout(BlockId::Tag(BlockTag::Latest)).await?; + info!("Registered model: {}", name); + + db.register_model(schema, layout, event.data[1]).await?; + + Ok(()) + } +} diff --git a/crates/torii/core/src/processors/register_system.rs b/crates/torii/core/src/processors/register_system.rs index 9a3ce836f0..a7e29b9f5d 100644 --- a/crates/torii/core/src/processors/register_system.rs +++ b/crates/torii/core/src/processors/register_system.rs @@ -1,42 +1,43 @@ use anyhow::{Error, Ok, Result}; use async_trait::async_trait; use dojo_world::manifest::System; -use starknet::core::types::{BlockWithTxs, Event, TransactionReceipt}; +use starknet::core::types::{BlockWithTxs, Event, InvokeTransactionReceipt}; use starknet::core::utils::parse_cairo_short_string; -use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcTransport}; +use starknet::providers::Provider; +use torii_client::contract::world::WorldContractReader; use tracing::info; use super::EventProcessor; -use crate::State; +use crate::sql::Sql; #[derive(Default)] pub struct RegisterSystemProcessor; #[async_trait] -impl EventProcessor for RegisterSystemProcessor { +impl EventProcessor

for RegisterSystemProcessor { fn event_key(&self) -> String { "SystemRegistered".to_string() } async fn process( &self, - storage: &S, - _provider: &JsonRpcClient, + _world: &WorldContractReader<'_, P>, + db: &Sql, + _provider: &P, _block: &BlockWithTxs, - _transaction_receipt: &TransactionReceipt, + _invoke_receipt: &InvokeTransactionReceipt, event: &Event, ) -> Result<(), Error> { let name = parse_cairo_short_string(&event.data[0])?; info!("registered system: {}", name); - storage - .register_system(System { - name: name.into(), - class_hash: event.data[1], - ..System::default() - }) - .await?; + db.register_system(System { + name: name.into(), + class_hash: event.data[1], + ..System::default() + }) + .await?; Ok(()) } diff --git a/crates/torii/core/src/processors/store_set_record.rs b/crates/torii/core/src/processors/store_set_record.rs index 7194db49e0..ff178dcca0 100644 --- a/crates/torii/core/src/processors/store_set_record.rs +++ b/crates/torii/core/src/processors/store_set_record.rs @@ -1,41 +1,43 @@ use anyhow::{Error, Ok, Result}; use async_trait::async_trait; -use starknet::core::types::{BlockWithTxs, Event, TransactionReceipt}; +use starknet::core::types::{BlockWithTxs, Event, InvokeTransactionReceipt}; use starknet::core::utils::parse_cairo_short_string; -use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcTransport}; +use starknet::providers::Provider; use starknet_crypto::FieldElement; +use torii_client::contract::world::WorldContractReader; use tracing::info; use super::EventProcessor; -use crate::State; +use crate::sql::Sql; #[derive(Default)] pub struct StoreSetRecordProcessor; -const COMPONENT_INDEX: usize = 0; +const MODEL_INDEX: usize = 0; const NUM_KEYS_INDEX: usize = 1; #[async_trait] -impl EventProcessor for StoreSetRecordProcessor { +impl EventProcessor

for StoreSetRecordProcessor { fn event_key(&self) -> String { "StoreSetRecord".to_string() } async fn process( &self, - storage: &S, - _provider: &JsonRpcClient, + _world: &WorldContractReader<'_, P>, + db: &Sql, + _provider: &P, _block: &BlockWithTxs, - _transaction_receipt: &TransactionReceipt, + _transaction_receipt: &InvokeTransactionReceipt, event: &Event, ) -> Result<(), Error> { - let name = parse_cairo_short_string(&event.data[COMPONENT_INDEX])?; + let name = parse_cairo_short_string(&event.data[MODEL_INDEX])?; info!("store set record: {}", name); let keys = values_at(&event.data, NUM_KEYS_INDEX)?; let values_index = keys.len() + NUM_KEYS_INDEX + 2; let values = values_at(&event.data, values_index)?; - storage.set_entity(name, keys, values).await?; + db.set_entity(name, keys, values).await?; Ok(()) } } diff --git a/crates/torii/core/src/processors/store_system_call.rs b/crates/torii/core/src/processors/store_system_call.rs new file mode 100644 index 0000000000..b0cfdb8c20 --- /dev/null +++ b/crates/torii/core/src/processors/store_system_call.rs @@ -0,0 +1,56 @@ +use std::str::FromStr; + +use anyhow::{Error, Ok, Result}; +use async_trait::async_trait; +use starknet::core::types::{ + BlockWithTxs, FieldElement, InvokeTransaction, Transaction, TransactionReceipt, +}; +use starknet::core::utils::parse_cairo_short_string; +use starknet::providers::Provider; + +use super::TransactionProcessor; +use crate::sql::Sql; + +#[derive(Default)] +pub struct StoreSystemCallProcessor; + +const SYSTEM_NAME_OFFSET: usize = 6; +const ENTRYPOINT_OFFSET: usize = 2; +const EXECUTE_ENTRYPOINT: &str = + "0x240060cdb34fcc260f41eac7474ee1d7c80b7e3607daff9ac67c7ea2ebb1c44"; + +#[async_trait] +impl TransactionProcessor

for StoreSystemCallProcessor { + async fn process( + &self, + db: &Sql, + _provider: &P, + block: &BlockWithTxs, + transaction_receipt: &TransactionReceipt, + ) -> Result<(), Error> { + if let TransactionReceipt::Invoke(_) = transaction_receipt { + for tx in &block.transactions { + if let Some((tx_hash, system_name, calldata)) = parse_transaction(tx) { + let system_name = parse_cairo_short_string(&system_name)?; + + db.store_system_call(system_name, tx_hash, calldata).await?; + } + } + } + + Ok(()) + } +} + +fn parse_transaction( + transaction: &Transaction, +) -> Option<(FieldElement, FieldElement, &Vec)> { + if let Transaction::Invoke(InvokeTransaction::V1(tx)) = transaction { + let entrypoint_felt = FieldElement::from_str(EXECUTE_ENTRYPOINT).unwrap(); + if tx.calldata[ENTRYPOINT_OFFSET] == entrypoint_felt { + return Some((tx.transaction_hash, tx.calldata[SYSTEM_NAME_OFFSET], &tx.calldata)); + } + } + + None +} diff --git a/crates/torii/core/src/simple_broker.rs b/crates/torii/core/src/simple_broker.rs new file mode 100644 index 0000000000..d254c3008b --- /dev/null +++ b/crates/torii/core/src/simple_broker.rs @@ -0,0 +1,75 @@ +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::pin::Pin; +use std::sync::Mutex; +use std::task::{Context, Poll}; + +use futures_channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use futures_util::{Stream, StreamExt}; +use once_cell::sync::Lazy; +use slab::Slab; + +static SUBSCRIBERS: Lazy>>> = Lazy::new(Default::default); + +pub struct Senders(pub Slab>); + +struct BrokerStream(usize, UnboundedReceiver); + +fn with_senders(f: F) -> R +where + T: Sync + Send + Clone + 'static, + F: FnOnce(&mut Senders) -> R, +{ + let mut map = SUBSCRIBERS.lock().unwrap(); + let senders = map + .entry(TypeId::of::>()) + .or_insert_with(|| Box::new(Senders::(Default::default()))); + f(senders.downcast_mut::>().unwrap()) +} + +impl Drop for BrokerStream { + fn drop(&mut self) { + with_senders::(|senders| senders.0.remove(self.0)); + } +} + +impl Stream for BrokerStream { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.1.poll_next_unpin(cx) + } +} + +/// A simple broker based on memory +pub struct SimpleBroker(PhantomData); + +impl SimpleBroker { + /// Publish a message that all subscription streams can receive. + pub fn publish(msg: T) { + with_senders::(|senders| { + for (_, sender) in senders.0.iter_mut() { + sender.start_send(msg.clone()).ok(); + } + }); + } + + /// Subscribe to the message of the specified type and returns a `Stream`. + pub fn subscribe() -> impl Stream { + with_senders::(|senders| { + let (tx, rx) = mpsc::unbounded(); + let id = senders.0.insert(tx); + BrokerStream(id, rx) + }) + } + + /// Execute the given function with the _subscribers_ of the specified subscription type. + pub fn with_subscribers(f: F) -> R + where + T: Sync + Send + Clone + 'static, + F: FnOnce(&mut Senders) -> R, + { + with_senders(f) + } +} diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 46be049511..768b9f67be 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -1,14 +1,21 @@ +use std::str::FromStr; + use anyhow::Result; use async_trait::async_trait; -use dojo_world::manifest::{Component, Manifest, System}; +use chrono::{DateTime, Utc}; +use dojo_types::core::CairoType; +use dojo_types::model::Ty; +use dojo_world::manifest::{Manifest, System}; use sqlx::pool::PoolConnection; use sqlx::sqlite::SqliteRow; use sqlx::{Executor, Pool, Row, Sqlite}; -use starknet::core::types::FieldElement; +use starknet::core::types::{Event, FieldElement}; use starknet_crypto::poseidon_hash_many; use tokio::sync::Mutex; -use super::{State, World}; +use super::World; +use crate::simple_broker::SimpleBroker; +use crate::types::{Entity, Model as ModelType}; #[cfg(test)] #[path = "sql_test.rs"] @@ -49,38 +56,8 @@ impl Sql { Ok(Self { pool, world_address, query_queue: Mutex::new(vec![]) }) } -} - -#[async_trait] -impl Executable for Sql { - async fn queue(&self, queries: Vec) { - let mut query_queue = self.query_queue.lock().await; - query_queue.extend(queries); - } - - async fn execute(&self) -> Result<()> { - let queries; - { - let mut query_queue = self.query_queue.lock().await; - queries = query_queue.clone(); - query_queue.clear(); - } - - let mut tx = self.pool.begin().await?; - - for query in queries { - tx.execute(sqlx::query(&query)).await?; - } - - tx.commit().await?; - - Ok(()) - } -} -#[async_trait] -impl State for Sql { - async fn load_from_manifest(&self, manifest: Manifest) -> Result<()> { + pub async fn load_from_manifest(&self, manifest: Manifest) -> Result<()> { let mut updates = vec![ format!("world_address = '{:#x}'", self.world_address), format!("world_class_hash = '{:#x}'", manifest.world.class_hash), @@ -98,10 +75,6 @@ impl State for Sql { )]) .await; - for component in manifest.components { - self.register_component(component).await?; - } - for system in manifest.systems { self.register_system(system).await?; } @@ -109,7 +82,7 @@ impl State for Sql { self.execute().await } - async fn head(&self) -> Result { + pub async fn head(&self) -> Result { let mut conn: PoolConnection = self.pool.acquire().await?; let indexer: (i64,) = sqlx::query_as(&format!( "SELECT head FROM indexers WHERE id = '{:#x}'", @@ -120,7 +93,7 @@ impl State for Sql { Ok(indexer.0.try_into().expect("doesnt fit in u64")) } - async fn set_head(&self, head: u64) -> Result<()> { + pub async fn set_head(&self, head: u64) -> Result<()> { self.queue(vec![format!( "UPDATE indexers SET head = {head} WHERE id = '{:#x}'", self.world_address @@ -129,7 +102,7 @@ impl State for Sql { Ok(()) } - async fn world(&self) -> Result { + pub async fn world(&self) -> Result { let mut conn: PoolConnection = self.pool.acquire().await?; let meta: World = sqlx::query_as(&format!("SELECT * FROM worlds WHERE id = '{:#x}'", self.world_address)) @@ -139,7 +112,7 @@ impl State for Sql { Ok(meta) } - async fn set_world(&self, world: World) -> Result<()> { + pub async fn set_world(&self, world: World) -> Result<()> { self.queue(vec![format!( "UPDATE worlds SET world_address='{:#x}', world_class_hash='{:#x}', \ executor_address='{:#x}', executor_class_hash='{:#x}' WHERE id = '{:#x}'", @@ -153,65 +126,69 @@ impl State for Sql { Ok(()) } - async fn register_component(&self, component: Component) -> Result<()> { - let component_id = component.name.to_lowercase(); - let mut queries = vec![format!( - "INSERT INTO components (id, name, class_hash) VALUES ('{}', '{}', '{:#x}') ON \ - CONFLICT(id) DO UPDATE SET class_hash='{:#x}'", - component_id, component.name, component.class_hash, component.class_hash - )]; - - let mut component_table_query = format!( - "CREATE TABLE IF NOT EXISTS external_{} (entity_id TEXT NOT NULL PRIMARY KEY, ", - component.name.to_lowercase() - ); + pub async fn register_model( + &self, + schema: Ty, + layout: Vec, + class_hash: FieldElement, + ) -> Result<()> { + let types = schema.flatten(); - for member in component.clone().members { - if member.key { - continue; - } + let root = types.first().unwrap(); + let root_name = root.name(); - component_table_query.push_str(&format!( - "external_{} {}, ", - member.name, - sql_type(&member.ty)? - )); - } + let layout_blob = layout.iter().map(|x| (*x).try_into().unwrap()).collect::>(); + let mut queries = vec![format!( + "INSERT INTO models (id, name, class_hash, layout) VALUES ('{}', '{}', '{:#x}', '{}') \ + ON CONFLICT(id) DO UPDATE SET class_hash='{:#x}'", + root_name, + root_name, + class_hash, + hex::encode(&layout_blob), + class_hash + )]; + queries.extend(build_model_query(root, 0, None)); - component_table_query.push_str( - "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (entity_id) REFERENCES entities(id));", - ); - queries.push(component_table_query); - - for member in component.members { - queries.push(format!( - "INSERT OR IGNORE INTO component_members (component_id, name, type, key) VALUES \ - ('{}', '{}', '{}', {})", - component_id, member.name, member.ty, member.key, - )); + for (model_idx, ty) in types[1..].iter().enumerate() { + queries.extend(build_model_query(ty, model_idx + 1, Some(root_name.clone()))); } self.queue(queries).await; + + // Since previous query has not been executed, we have to make sure created_at exists + let created_at: DateTime = + match sqlx::query("SELECT created_at FROM models WHERE id = ?") + .bind(root_name.clone()) + .fetch_one(&self.pool) + .await + { + Ok(query_result) => query_result.try_get("created_at")?, + Err(_) => Utc::now(), + }; + + SimpleBroker::publish(ModelType { + id: root_name.clone(), + name: root_name, + class_hash: format!("{:#x}", class_hash), + transaction_hash: "0x0".to_string(), + created_at, + }); Ok(()) } - async fn register_system(&self, system: System) -> Result<()> { + pub async fn register_system(&self, system: System) -> Result<()> { let query = format!( "INSERT INTO systems (id, name, class_hash) VALUES ('{}', '{}', '{:#x}') ON \ CONFLICT(id) DO UPDATE SET class_hash='{:#x}'", - system.name.to_lowercase(), - system.name, - system.class_hash, - system.class_hash + system.name, system.name, system.class_hash, system.class_hash ); self.queue(vec![query]).await; Ok(()) } - async fn set_entity( + pub async fn set_entity( &self, - component: String, + model: String, keys: Vec, values: Vec, ) -> Result<()> { @@ -221,117 +198,214 @@ impl State for Sql { .fetch_optional(&self.pool) .await?; - // TODO: map keys to individual columns - let keys_str = keys.iter().map(|k| format!("{:#x}", k)).collect::>().join(","); - let component_names = component_names(entity_result, &component)?; - let insert_entities = format!( - "INSERT INTO entities (id, keys, component_names) VALUES ('{}', '{}', '{}') ON \ - CONFLICT(id) DO UPDATE SET - component_names=excluded.component_names, + let keys_str = felts_sql_string(&keys); + let model_names = model_names_sql_string(entity_result, &model)?; + let mut queries = vec![format!( + "INSERT INTO entities (id, keys, model_names) VALUES ('{}', '{}', '{}') ON \ + CONFLICT(id) DO UPDATE SET model_names=excluded.model_names, \ updated_at=CURRENT_TIMESTAMP", - entity_id, keys_str, component_names - ); + entity_id, keys_str, model_names + )]; - let member_results = sqlx::query( - "SELECT * FROM component_members WHERE key == FALSE AND component_id = ? ORDER BY id \ - ASC", + let members: Vec<(String, String, String)> = sqlx::query_as( + "SELECT id, name, type FROM model_members WHERE model_id = ? ORDER BY model_idx, \ + member_idx ASC", ) - .bind(component.to_lowercase()) + .bind(model.clone()) .fetch_all(&self.pool) .await?; - let (names_str, values_str) = format_values(member_results, values)?; - let insert_components = format!( - "INSERT OR REPLACE INTO external_{} (entity_id {}) VALUES ('{}' {})", - component.to_lowercase(), - names_str, - entity_id, - values_str - ); + let (primitive_members, _): (Vec<_>, Vec<_>) = + members.into_iter().partition(|member| CairoType::from_str(&member.2).is_ok()); + + // keys are part of model members, so combine keys and model values array + let mut member_values: Vec = Vec::new(); + member_values.extend(keys.clone()); + member_values.extend(values); + + let insert_models: Vec<_> = primitive_members + .into_iter() + .zip(member_values.into_iter()) + .map(|((id, name, ty), value)| { + format!( + "INSERT OR REPLACE INTO [{id}] (entity_id, external_{name}) VALUES \ + ('{entity_id}' {})", + CairoType::from_str(&ty).unwrap().format_for_sql(vec![&value]).unwrap() + ) + }) + .collect(); + + queries.extend(insert_models); // tx commit required - self.queue(vec![insert_entities, insert_components]).await; + self.queue(queries).await; self.execute().await?; + + let query_result = sqlx::query("SELECT created_at FROM entities WHERE id = ?") + .bind(entity_id.clone()) + .fetch_one(&self.pool) + .await?; + let created_at: DateTime = query_result.try_get("created_at")?; + + SimpleBroker::publish(Entity { + id: entity_id.clone(), + keys: keys_str, + model_names, + created_at, + updated_at: Utc::now(), + }); Ok(()) } - async fn delete_entity(&self, component: String, key: FieldElement) -> Result<()> { - let query = format!("DELETE FROM {component} WHERE id = {key}"); + pub async fn delete_entity(&self, model: String, key: FieldElement) -> Result<()> { + let query = format!("DELETE FROM {model} WHERE id = {key}"); self.queue(vec![query]).await; Ok(()) } - async fn entity(&self, component: String, key: FieldElement) -> Result> { - let query = format!("SELECT * FROM {component} WHERE id = {key}"); + pub async fn entity(&self, model: String, key: FieldElement) -> Result> { + let query = format!("SELECT * FROM {model} WHERE id = {key}"); let mut conn: PoolConnection = self.pool.acquire().await?; let row: (i32, String, String) = sqlx::query_as(&query).fetch_one(&mut conn).await?; Ok(serde_json::from_str(&row.2).unwrap()) } - async fn entities(&self, component: String) -> Result>> { - let query = format!("SELECT * FROM {component}"); + pub async fn entities(&self, model: String) -> Result>> { + let query = format!("SELECT * FROM {model}"); let mut conn: PoolConnection = self.pool.acquire().await?; let mut rows = sqlx::query_as::<_, (i32, String, String)>(&query).fetch_all(&mut conn).await?; Ok(rows.drain(..).map(|row| serde_json::from_str(&row.2).unwrap()).collect()) } + + pub async fn store_system_call( + &self, + system: String, + transaction_hash: FieldElement, + calldata: &[FieldElement], + ) -> Result<()> { + let query = format!( + "INSERT OR IGNORE INTO system_calls (data, transaction_hash, system_id) VALUES ('{}', \ + '{:#x}', '{}')", + calldata.iter().map(|c| format!("{:#x}", c)).collect::>().join(","), + transaction_hash, + system + ); + self.queue(vec![query]).await; + Ok(()) + } + + pub async fn store_event( + &self, + event: &Event, + event_idx: usize, + transaction_hash: FieldElement, + ) -> Result<()> { + let keys_str = felts_sql_string(&event.keys); + let data_str = felts_sql_string(&event.data); + + let id = format!("{:#x}:{}", transaction_hash, event_idx); + let query = format!( + "INSERT OR IGNORE INTO events (id, keys, data, transaction_hash) VALUES ('{}', '{}', \ + '{}', '{:#x}')", + id, keys_str, data_str, transaction_hash + ); + + self.queue(vec![query]).await; + Ok(()) + } +} + +#[async_trait] +impl Executable for Sql { + async fn queue(&self, queries: Vec) { + let mut query_queue = self.query_queue.lock().await; + query_queue.extend(queries); + } + + async fn execute(&self) -> Result<()> { + let queries; + { + let mut query_queue = self.query_queue.lock().await; + queries = query_queue.clone(); + query_queue.clear(); + } + + let mut tx = self.pool.begin().await?; + for query in queries { + tx.execute(sqlx::query(&query)).await?; + } + + tx.commit().await?; + + Ok(()) + } } -fn component_names(entity_result: Option, new_component: &str) -> Result { - let component_names = match entity_result { +fn model_names_sql_string(entity_result: Option, new_model: &str) -> Result { + let model_names = match entity_result { Some(entity) => { - let existing = entity.try_get::("component_names")?; - if existing.contains(new_component) { + let existing = entity.try_get::("model_names")?; + if existing.contains(new_model) { existing } else { - format!("{},{}", existing, new_component) + format!("{},{}", existing, new_model) } } - None => new_component.to_string(), + None => new_model.to_string(), }; - Ok(component_names) + Ok(model_names) } -fn format_values( - member_results: Vec, - values: Vec, -) -> Result<(String, String)> { - let names: Result> = member_results - .iter() - .map(|row| { - let name = row.try_get::("name")?; - Ok(format!(",external_{}", name)) - }) - .collect(); - - let types: Result> = - member_results.iter().map(|row| Ok(row.try_get::("type")?)).collect(); - - // format according to type - let values: Result> = values - .iter() - .zip(types?.iter()) - .map(|(value, ty)| { - if sql_type(ty)? == "INTEGER" { - Ok(format!(",'{}'", value)) - } else { - Ok(format!(",'{:#x}'", value)) - } - }) - .collect(); - - Ok((names?.join(""), values?.join(""))) +fn felts_sql_string(felts: &[FieldElement]) -> String { + felts.iter().map(|k| format!("{:#x}", k)).collect::>().join("/") + "/" } -// NOTE: If adding/removing types, corresponding change needs to be made to torii-graphql -// `src/types.rs` -fn sql_type(member_type: &str) -> Result<&str, anyhow::Error> { - match member_type { - "u8" | "u16" | "u32" | "u64" | "usize" | "bool" => Ok("INTEGER"), - "u128" | "u256" | "Cursor" | "ContractAddress" | "ClassHash" | "DateTime" | "felt252" => { - Ok("TEXT") +fn build_model_query(model: &Ty, model_idx: usize, parent_id: Option) -> Vec { + let name = if let Some(parent_id) = parent_id.clone() { + format!("{parent_id}${}", model.name()) + } else { + model.name() + }; + let model_id = if let Some(parent_id) = parent_id.clone() { parent_id } else { model.name() }; + + let mut queries = vec![]; + let mut query = + format!("CREATE TABLE IF NOT EXISTS [{}] (entity_id TEXT NOT NULL PRIMARY KEY, ", name); + + match model { + Ty::Struct(s) => { + for (member_idx, member) in s.children.iter().enumerate() { + if let Ok(cairo_type) = CairoType::from_str(&member.ty.name()) { + query.push_str(&format!( + "external_{} {}, ", + member.name, + cairo_type.to_sql_type() + )); + }; + + queries.push(format!( + "INSERT OR IGNORE INTO model_members (id, model_id, model_idx, member_idx, \ + name, type, key) VALUES ('{name}', '{model_id}', '{model_idx}', \ + '{member_idx}', '{}', '{}', {})", + member.name, + member.ty.name(), + member.key, + )); + } } - _ => Err(anyhow::anyhow!("Unknown member type {}", member_type.to_string())), + Ty::Enum(_) => {} + _ => {} } + + query.push_str("created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); + + if let Some(id) = parent_id { + query.push_str(&format!("FOREIGN KEY (entity_id) REFERENCES {id} (entity_id), ")); + }; + + query.push_str("FOREIGN KEY (entity_id) REFERENCES entities(id));"); + queries.push(query); + queries } diff --git a/crates/torii/core/src/sql_test.rs b/crates/torii/core/src/sql_test.rs index d4b2f53cd1..96163e0582 100644 --- a/crates/torii/core/src/sql_test.rs +++ b/crates/torii/core/src/sql_test.rs @@ -1,11 +1,10 @@ use camino::Utf8PathBuf; -use dojo_types::component::Member; -use dojo_world::manifest::{Component, System}; +use dojo_types::model::{Member, Struct, Ty}; +use dojo_world::manifest::System; use sqlx::sqlite::SqlitePool; -use starknet::core::types::FieldElement; +use starknet::core::types::{Event, FieldElement}; use crate::sql::{Executable, Sql}; -use crate::State; #[sqlx::test(migrations = "../migrations")] async fn test_load_from_manifest(pool: SqlitePool) { @@ -18,15 +17,8 @@ async fn test_load_from_manifest(pool: SqlitePool) { let state = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); state.load_from_manifest(manifest.clone()).await.unwrap(); - let components = sqlx::query("SELECT * FROM components").fetch_all(&pool).await.unwrap(); - assert_eq!(components.len(), 2); - - let moves_components = - sqlx::query("SELECT * FROM external_moves").fetch_all(&pool).await.unwrap(); - assert_eq!(moves_components.len(), 0); - - let systems = sqlx::query("SELECT * FROM systems").fetch_all(&pool).await.unwrap(); - assert_eq!(systems.len(), 3); + let models = sqlx::query("SELECT * FROM models").fetch_all(&pool).await.unwrap(); + assert_eq!(models.len(), 0); let mut world = state.world().await.unwrap(); @@ -52,33 +44,38 @@ async fn test_load_from_manifest(pool: SqlitePool) { assert_eq!(head, 1); state - .register_component(Component { - name: "Test".into(), - members: vec![Member { name: "test".into(), ty: "u32".into(), key: false }], - class_hash: FieldElement::TWO, - ..Default::default() - }) + .register_model( + Ty::Struct(Struct { + name: "Position".into(), + children: vec![Member { + name: "test".into(), + ty: Ty::Terminal("u32".to_string()), + key: false, + }], + }), + vec![], + FieldElement::TWO, + ) .await .unwrap(); state.execute().await.unwrap(); let (id, name, class_hash): (String, String, String) = - sqlx::query_as("SELECT id, name, class_hash FROM components WHERE id = 'test'") + sqlx::query_as("SELECT id, name, class_hash FROM models WHERE id = 'Position'") .fetch_one(&pool) .await .unwrap(); - assert_eq!(id, "test"); - assert_eq!(name, "Test"); + assert_eq!(id, "Position"); + assert_eq!(name, "Position"); assert_eq!(class_hash, format!("{:#x}", FieldElement::TWO)); - let test_components = - sqlx::query("SELECT * FROM external_test").fetch_all(&pool).await.unwrap(); - assert_eq!(test_components.len(), 0); + let position_models = sqlx::query("SELECT * FROM [Position]").fetch_all(&pool).await.unwrap(); + assert_eq!(position_models.len(), 0); state .register_system(System { - name: "Test".into(), + name: "Position".into(), inputs: vec![], outputs: vec![], class_hash: FieldElement::THREE, @@ -90,13 +87,13 @@ async fn test_load_from_manifest(pool: SqlitePool) { state.execute().await.unwrap(); let (id, name, class_hash): (String, String, String) = - sqlx::query_as("SELECT id, name, class_hash FROM systems WHERE id = 'test'") + sqlx::query_as("SELECT id, name, class_hash FROM systems WHERE id = 'Position'") .fetch_one(&pool) .await .unwrap(); - assert_eq!(id, "test"); - assert_eq!(name, "Test"); + assert_eq!(id, "Position"); + assert_eq!(name, "Position"); assert_eq!(class_hash, format!("{:#x}", FieldElement::THREE)); state @@ -111,5 +108,35 @@ async fn test_load_from_manifest(pool: SqlitePool) { ) .await .unwrap(); + + // state + // .store_system_call( + // "Test".into(), + // FieldElement::from_str("0x4").unwrap(), + // &[FieldElement::ONE, FieldElement::TWO, FieldElement::THREE], + // ) + // .await + // .unwrap(); + + state + .store_event( + &Event { + from_address: FieldElement::ONE, + keys: Vec::from([FieldElement::TWO]), + data: Vec::from([FieldElement::TWO, FieldElement::THREE]), + }, + 0, + FieldElement::THREE, + ) + .await + .unwrap(); + state.execute().await.unwrap(); + + let keys = format!("{:#x}/", FieldElement::TWO); + let query = format!("SELECT data, transaction_hash FROM events WHERE keys = '{}'", keys); + let (data, tx_hash): (String, String) = sqlx::query_as(&query).fetch_one(&pool).await.unwrap(); + + assert_eq!(data, format!("{:#x}/{:#x}/", FieldElement::TWO, FieldElement::THREE)); + assert_eq!(tx_hash, format!("{:#x}", FieldElement::THREE)) } diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index 569134f33e..62c7da91c4 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -1,6 +1,8 @@ use core::fmt; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use sqlx::FromRow; use starknet::core::types::FieldElement; #[derive(Serialize, Deserialize)] @@ -25,3 +27,23 @@ impl fmt::LowerHex for SQLFieldElement { self.0.fmt(f) } } + +#[derive(FromRow, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Entity { + pub id: String, + pub keys: String, + pub model_names: String, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(FromRow, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Model { + pub id: String, + pub name: String, + pub class_hash: String, + pub transaction_hash: String, + pub created_at: DateTime, +} diff --git a/crates/torii/graphql/Cargo.toml b/crates/torii/graphql/Cargo.toml index 2498a99c94..05e33083c2 100644 --- a/crates/torii/graphql/Cargo.toml +++ b/crates/torii/graphql/Cargo.toml @@ -11,25 +11,29 @@ version.workspace = true [dependencies] anyhow.workspace = true async-graphql = { version = "5.0.8", features = [ "chrono", "dynamic-schema" ] } -async-graphql-poem = "5.0.8" +async-graphql-warp = "5.0.8" async-trait.workspace = true base64 = "0.21.2" chrono.workspace = true indexmap = "1.9.3" -log = "0.4.17" -poem = "1.3.48" +scarb-ui.workspace = true serde.workspace = true serde_json.workspace = true +sozo = { path = "../../sozo" } sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] } -tokio = { version = "1.20.1", features = [ "full" ] } tokio-stream = "0.1.11" tokio-util = "0.7.7" +tokio.workspace = true +torii-core = { path = "../core" } tracing.workspace = true -url = "2.2.2" +url.workspace = true +warp.workspace = true [dev-dependencies] camino.workspace = true +dojo-test-utils = { path = "../../dojo-test-utils" } +dojo-types = { path = "../../dojo-types" } dojo-world = { path = "../../dojo-world" } starknet-crypto.workspace = true starknet.workspace = true -torii-core = { path = "../core" } +torii-client = { path = "../client" } diff --git a/crates/torii/graphql/src/lib.rs b/crates/torii/graphql/src/lib.rs index e1140a015d..15bc2e9f16 100644 --- a/crates/torii/graphql/src/lib.rs +++ b/crates/torii/graphql/src/lib.rs @@ -1,8 +1,9 @@ mod constants; -mod object; +pub mod object; + mod query; +pub mod route; pub mod schema; -pub mod server; pub mod types; mod utils; diff --git a/crates/torii/graphql/src/object/component.rs b/crates/torii/graphql/src/object/component.rs deleted file mode 100644 index 8484d9768a..0000000000 --- a/crates/torii/graphql/src/object/component.rs +++ /dev/null @@ -1,103 +0,0 @@ -use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; -use async_graphql::{Name, Value}; -use chrono::{DateTime, Utc}; -use indexmap::IndexMap; -use serde::Deserialize; -use sqlx::{FromRow, Pool, Sqlite}; - -use super::connection::connection_output; -use super::{ObjectTrait, TypeMapping, ValueMapping}; -use crate::constants::DEFAULT_LIMIT; -use crate::query::{query_all, query_by_id, query_total_count, ID}; -use crate::types::ScalarType; - -#[derive(FromRow, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Component { - pub id: String, - pub name: String, - pub class_hash: String, - pub transaction_hash: String, - pub created_at: DateTime, -} - -pub struct ComponentObject { - pub type_mapping: TypeMapping, -} - -impl ComponentObject { - // Not used currently, eventually used for component metadata - pub fn _new() -> Self { - Self { - type_mapping: IndexMap::from([ - (Name::new("id"), TypeRef::named(TypeRef::ID)), - (Name::new("name"), TypeRef::named(TypeRef::STRING)), - (Name::new("classHash"), TypeRef::named(ScalarType::Felt252.to_string())), - (Name::new("transactionHash"), TypeRef::named(ScalarType::Felt252.to_string())), - (Name::new("createdAt"), TypeRef::named(ScalarType::DateTime.to_string())), - ]), - } - } - - pub fn value_mapping(component: Component) -> ValueMapping { - IndexMap::from([ - (Name::new("id"), Value::from(component.id)), - (Name::new("name"), Value::from(component.name)), - (Name::new("classHash"), Value::from(component.class_hash)), - (Name::new("transactionHash"), Value::from(component.transaction_hash)), - ( - Name::new("createdAt"), - Value::from(component.created_at.format("%Y-%m-%d %H:%M:%S").to_string()), - ), - ]) - } -} - -impl ObjectTrait for ComponentObject { - fn name(&self) -> &str { - "component" - } - - fn type_name(&self) -> &str { - "Component" - } - - fn type_mapping(&self) -> &TypeMapping { - &self.type_mapping - } - - fn resolve_one(&self) -> Option { - Some( - Field::new(self.name(), TypeRef::named_nn(self.type_name()), |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let id = ctx.args.try_get("id")?.string()?.to_string(); - let component = query_by_id(&mut conn, "components", ID::Str(id)).await?; - let result = ComponentObject::value_mapping(component); - Ok(Some(Value::Object(result))) - }) - }) - .argument(InputValue::new("id", TypeRef::named_nn(TypeRef::ID))), - ) - } - - fn resolve_many(&self) -> Option { - Some(Field::new( - "components", - TypeRef::named(format!("{}Connection", self.type_name())), - |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let total_count = - query_total_count(&mut conn, "components", &Vec::new()).await?; - let data: Vec = - query_all(&mut conn, "components", DEFAULT_LIMIT).await?; - let components: Vec = - data.into_iter().map(ComponentObject::value_mapping).collect(); - - Ok(Some(Value::Object(connection_output(components, total_count)))) - }) - }, - )) - } -} diff --git a/crates/torii/graphql/src/object/connection/page_info.rs b/crates/torii/graphql/src/object/connection/page_info.rs index 7af7bb6b08..ca88d744de 100644 --- a/crates/torii/graphql/src/object/connection/page_info.rs +++ b/crates/torii/graphql/src/object/connection/page_info.rs @@ -9,8 +9,8 @@ pub struct PageInfoObject { pub type_mapping: TypeMapping, } -impl PageInfoObject { - pub fn new() -> Self { +impl Default for PageInfoObject { + fn default() -> Self { Self { type_mapping: IndexMap::from([ (Name::new("hasPreviousPage"), TypeRef::named(TypeRef::BOOLEAN)), diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 7499c9e6c0..c722195c62 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -1,16 +1,19 @@ -use async_graphql::dynamic::{Field, FieldFuture, FieldValue, InputValue, TypeRef}; +use async_graphql::dynamic::{ + Field, FieldFuture, FieldValue, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, +}; use async_graphql::{Name, Value}; -use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use serde::Deserialize; use sqlx::pool::PoolConnection; -use sqlx::{FromRow, Pool, Result, Sqlite}; +use sqlx::{Pool, Result, Sqlite}; +use tokio_stream::StreamExt; +use torii_core::simple_broker::SimpleBroker; +use torii_core::types::Entity; -use super::component_state::{component_state_by_id_query, type_mapping_query}; use super::connection::{ connection_arguments, connection_output, decode_cursor, parse_connection_arguments, ConnectionArguments, }; +use super::model_state::{model_state_by_id_query, type_mapping_query}; use super::{ObjectTrait, TypeMapping, ValueMapping}; use crate::constants::DEFAULT_LIMIT; use crate::query::{query_by_id, ID}; @@ -18,39 +21,31 @@ use crate::types::ScalarType; use crate::utils::csv_to_vec; use crate::utils::extract_value::extract; -#[derive(FromRow, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct Entity { - pub id: String, - pub keys: String, - pub component_names: String, - pub created_at: DateTime, - pub updated_at: DateTime, -} - pub struct EntityObject { pub type_mapping: TypeMapping, } -impl EntityObject { - pub fn new() -> Self { +impl Default for EntityObject { + fn default() -> Self { Self { type_mapping: IndexMap::from([ (Name::new("id"), TypeRef::named(TypeRef::ID)), (Name::new("keys"), TypeRef::named_list(TypeRef::STRING)), - (Name::new("componentNames"), TypeRef::named(TypeRef::STRING)), + (Name::new("modelNames"), TypeRef::named(TypeRef::STRING)), (Name::new("createdAt"), TypeRef::named(ScalarType::DateTime.to_string())), (Name::new("updatedAt"), TypeRef::named(ScalarType::DateTime.to_string())), ]), } } +} +impl EntityObject { pub fn value_mapping(entity: Entity) -> ValueMapping { - let keys: Vec<&str> = entity.keys.split(',').map(|s| s.trim()).collect(); + let keys: Vec<&str> = entity.keys.split('/').filter(|&k| !k.is_empty()).collect(); IndexMap::from([ (Name::new("id"), Value::from(entity.id)), (Name::new("keys"), Value::from(keys)), - (Name::new("componentNames"), Value::from(entity.component_names)), + (Name::new("modelNames"), Value::from(entity.model_names)), ( Name::new("createdAt"), Value::from(entity.created_at.format("%Y-%m-%d %H:%M:%S").to_string()), @@ -77,29 +72,24 @@ impl ObjectTrait for EntityObject { } fn nested_fields(&self) -> Option> { - Some(vec![Field::new("components", TypeRef::named_list("ComponentUnion"), move |ctx| { + Some(vec![Field::new("models", TypeRef::named_list("ModelUnion"), move |ctx| { FieldFuture::new(async move { match ctx.parent_value.try_to_value()? { Value::Object(indexmap) => { let mut conn = ctx.data::>()?.acquire().await?; - let components = - csv_to_vec(&extract::(indexmap, "componentNames")?); + let models = csv_to_vec(&extract::(indexmap, "modelNames")?); let id = extract::(indexmap, "id")?; let mut results: Vec> = Vec::new(); - for component_name in components { - let table_name = component_name.to_lowercase(); + for model_name in models { + let table_name = model_name.to_lowercase(); let type_mapping = type_mapping_query(&mut conn, &table_name).await?; - let state = component_state_by_id_query( - &mut conn, - &table_name, - &id, - &type_mapping, - ) - .await?; + let state = + model_state_by_id_query(&mut conn, &table_name, &id, &type_mapping) + .await?; results.push(FieldValue::with_type( FieldValue::owned_any(state), - component_name, + model_name, )); } @@ -155,6 +145,31 @@ impl ObjectTrait for EntityObject { Some(field) } + + fn subscriptions(&self) -> Option> { + let name = format!("{}Updated", self.name()); + Some(vec![ + SubscriptionField::new(name, TypeRef::named_nn(self.type_name()), |ctx| { + SubscriptionFieldFuture::new(async move { + let id = match ctx.args.get("id") { + Some(id) => Some(id.string()?.to_string()), + None => None, + }; + // if id is None, then subscribe to all entities + // if id is Some, then subscribe to only the entity with that id + Ok(SimpleBroker::::subscribe().filter_map(move |entity: Entity| { + if id.is_none() || id == Some(entity.id.clone()) { + Some(Ok(Value::Object(EntityObject::value_mapping(entity)))) + } else { + // id != entity.id , then don't send anything, still listening + None + } + })) + }) + }) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), + ]) + } } async fn entities_by_sk( @@ -167,8 +182,9 @@ async fn entities_by_sk( let mut conditions = Vec::new(); if let Some(keys) = &keys { - conditions.push(format!("keys LIKE '{}%'", keys.join(","))); - count_query.push_str(&format!(" WHERE keys LIKE '{}%'", keys.join(","))); + let keys_str = keys.join("/"); + conditions.push(format!("keys LIKE '{}/%'", keys_str)); + count_query.push_str(&format!(" WHERE keys LIKE '{}/%'", keys_str)); } if let Some(after_cursor) = &args.after { diff --git a/crates/torii/graphql/src/object/event.rs b/crates/torii/graphql/src/object/event.rs index c1bdfac0c2..decac57326 100644 --- a/crates/torii/graphql/src/object/event.rs +++ b/crates/torii/graphql/src/object/event.rs @@ -20,36 +20,37 @@ pub struct Event { pub keys: String, pub data: String, pub created_at: DateTime, - pub system_call_id: i64, + pub transaction_hash: String, } pub struct EventObject { pub type_mapping: TypeMapping, } -impl EventObject { - pub fn new() -> Self { +impl Default for EventObject { + fn default() -> Self { Self { type_mapping: IndexMap::from([ (Name::new("id"), TypeRef::named(TypeRef::ID)), (Name::new("keys"), TypeRef::named(TypeRef::STRING)), (Name::new("data"), TypeRef::named(TypeRef::STRING)), - (Name::new("systemCallId"), TypeRef::named(TypeRef::INT)), (Name::new("createdAt"), TypeRef::named(ScalarType::DateTime.to_string())), + (Name::new("transactionHash"), TypeRef::named(TypeRef::STRING)), ]), } } - +} +impl EventObject { pub fn value_mapping(event: Event) -> ValueMapping { IndexMap::from([ (Name::new("id"), Value::from(event.id)), (Name::new("keys"), Value::from(event.keys)), (Name::new("data"), Value::from(event.data)), - (Name::new("systemCallId"), Value::from(event.system_call_id)), ( Name::new("createdAt"), Value::from(event.created_at.format("%Y-%m-%d %H:%M:%S").to_string()), ), + (Name::new("transactionHash"), Value::from(event.transaction_hash)), ]) } } diff --git a/crates/torii/graphql/src/object/inputs/order_input.rs b/crates/torii/graphql/src/object/inputs/order_input.rs index ace95726f4..c4c4cdee30 100644 --- a/crates/torii/graphql/src/object/inputs/order_input.rs +++ b/crates/torii/graphql/src/object/inputs/order_input.rs @@ -38,7 +38,7 @@ impl InputObjectTrait for OrderInputObject { // Direction enum has only two members ASC and DESC let direction = Enum::new("Direction").item("ASC").item("DESC"); - // Field Order enum consist of all members of a component + // Field Order enum consist of all members of a model let field_order = self .type_mapping .iter() diff --git a/crates/torii/graphql/src/object/inputs/where_input.rs b/crates/torii/graphql/src/object/inputs/where_input.rs index 6fa62379c4..a5b2c804b6 100644 --- a/crates/torii/graphql/src/object/inputs/where_input.rs +++ b/crates/torii/graphql/src/object/inputs/where_input.rs @@ -15,7 +15,7 @@ pub struct WhereInputObject { impl WhereInputObject { // Iterate through an object's type mapping and create a new mapping for whereInput. For each of - // the object type (component member), we add 6 additional types for comparators (great than, + // the object type (model member), we add 6 additional types for comparators (great than, // not equal, etc). Only filter on our custom scalar types and ignore async-graphql's types. // Due to sqlite column constraints, u8 thru u64 are treated as numerics and the rest of the // types are treated as strings. diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index deb452c720..a8c61b038e 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -1,13 +1,15 @@ -pub mod component; -pub mod component_state; pub mod connection; pub mod entity; pub mod event; pub mod inputs; +pub mod model; +pub mod model_state; pub mod system; pub mod system_call; -use async_graphql::dynamic::{Enum, Field, FieldFuture, InputObject, Object, TypeRef}; +use async_graphql::dynamic::{ + Enum, Field, FieldFuture, InputObject, Object, SubscriptionField, TypeRef, +}; use async_graphql::{Error, Name, Value}; use indexmap::IndexMap; @@ -38,6 +40,10 @@ pub trait ObjectTrait { None } + // Resolves subscriptions, returns current object (eg "PlayerAdded") + fn subscriptions(&self) -> Option> { + None + } // Resolves plural object queries, returns type of {type_name}Connection (eg "PlayerConnection") fn resolve_many(&self) -> Option { None @@ -90,7 +96,7 @@ pub trait ObjectTrait { }; } - // Component union queries is a special case, it instead passes down a + // Model union queries is a special case, it instead passes down a // IndexMap. This could be avoided if // async-graphql allowed union resolver to be passed down as Value. if let Some(indexmap) = ctx.parent_value.downcast_ref::() { diff --git a/crates/torii/graphql/src/object/model.rs b/crates/torii/graphql/src/object/model.rs new file mode 100644 index 0000000000..35a5ada2ce --- /dev/null +++ b/crates/torii/graphql/src/object/model.rs @@ -0,0 +1,123 @@ +use async_graphql::dynamic::{ + Field, FieldFuture, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, +}; +use async_graphql::{Name, Value}; +use indexmap::IndexMap; +use sqlx::{Pool, Sqlite}; +use tokio_stream::StreamExt; +use torii_core::simple_broker::SimpleBroker; +use torii_core::types::Model; + +use super::connection::connection_output; +use super::{ObjectTrait, TypeMapping, ValueMapping}; +use crate::constants::DEFAULT_LIMIT; +use crate::query::{query_all, query_by_id, query_total_count, ID}; +use crate::types::ScalarType; + +pub struct ModelObject { + pub type_mapping: TypeMapping, +} + +impl Default for ModelObject { + // Eventually used for model metadata + fn default() -> Self { + Self { + type_mapping: IndexMap::from([ + (Name::new("id"), TypeRef::named(TypeRef::ID)), + (Name::new("name"), TypeRef::named(TypeRef::STRING)), + (Name::new("classHash"), TypeRef::named(ScalarType::Felt252.to_string())), + (Name::new("transactionHash"), TypeRef::named(ScalarType::Felt252.to_string())), + (Name::new("createdAt"), TypeRef::named(ScalarType::DateTime.to_string())), + ]), + } + } +} + +impl ModelObject { + pub fn value_mapping(model: Model) -> ValueMapping { + IndexMap::from([ + (Name::new("id"), Value::from(model.id)), + (Name::new("name"), Value::from(model.name)), + (Name::new("classHash"), Value::from(model.class_hash)), + (Name::new("transactionHash"), Value::from(model.transaction_hash)), + ( + Name::new("createdAt"), + Value::from(model.created_at.format("%Y-%m-%d %H:%M:%S").to_string()), + ), + ]) + } +} + +impl ObjectTrait for ModelObject { + fn name(&self) -> &str { + "model" + } + + fn type_name(&self) -> &str { + "Model" + } + + fn type_mapping(&self) -> &TypeMapping { + &self.type_mapping + } + + fn resolve_one(&self) -> Option { + Some( + Field::new(self.name(), TypeRef::named_nn(self.type_name()), |ctx| { + FieldFuture::new(async move { + let mut conn = ctx.data::>()?.acquire().await?; + let id = ctx.args.try_get("id")?.string()?.to_string(); + let model = query_by_id(&mut conn, "models", ID::Str(id)).await?; + let result = ModelObject::value_mapping(model); + Ok(Some(Value::Object(result))) + }) + }) + .argument(InputValue::new("id", TypeRef::named_nn(TypeRef::ID))), + ) + } + + fn resolve_many(&self) -> Option { + Some(Field::new( + "models", + TypeRef::named(format!("{}Connection", self.type_name())), + |ctx| { + FieldFuture::new(async move { + let mut conn = ctx.data::>()?.acquire().await?; + let total_count = query_total_count(&mut conn, "models", &Vec::new()).await?; + let data: Vec = query_all(&mut conn, "models", DEFAULT_LIMIT).await?; + let models: Vec = + data.into_iter().map(ModelObject::value_mapping).collect(); + + Ok(Some(Value::Object(connection_output(models, total_count)))) + }) + }, + )) + } + + fn subscriptions(&self) -> Option> { + let name = format!("{}Registered", self.name()); + Some(vec![ + SubscriptionField::new(name, TypeRef::named_nn(self.type_name()), |ctx| { + { + SubscriptionFieldFuture::new(async move { + let id = match ctx.args.get("id") { + Some(id) => Some(id.string()?.to_string()), + None => None, + }; + // if id is None, then subscribe to all models + // if id is Some, then subscribe to only the model with that id + Ok(SimpleBroker::::subscribe().filter_map(move |model: Model| { + if id.is_none() || id == Some(model.id.clone()) { + Some(Ok(Value::Object(ModelObject::value_mapping(model)))) + } else { + // id != model.id, so don't send anything, still listening + None + } + })) + }) + } + }) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), + ]) + } +} diff --git a/crates/torii/graphql/src/object/component_state.rs b/crates/torii/graphql/src/object/model_state.rs similarity index 89% rename from crates/torii/graphql/src/object/component_state.rs rename to crates/torii/graphql/src/object/model_state.rs index fb2fe879cb..81596d96f2 100644 --- a/crates/torii/graphql/src/object/component_state.rs +++ b/crates/torii/graphql/src/object/model_state.rs @@ -7,6 +7,7 @@ use serde::Deserialize; use sqlx::pool::PoolConnection; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Pool, QueryBuilder, Row, Sqlite}; +use torii_core::types::Entity; use super::connection::{ connection_arguments, decode_cursor, encode_cursor, parse_connection_arguments, @@ -17,7 +18,7 @@ use super::inputs::where_input::{parse_where_argument, where_argument, WhereInpu use super::inputs::InputObjectTrait; use super::{ObjectTrait, TypeMapping, ValueMapping}; use crate::constants::DEFAULT_LIMIT; -use crate::object::entity::{Entity, EntityObject}; +use crate::object::entity::EntityObject; use crate::query::filter::{Filter, FilterValue}; use crate::query::order::{Direction, Order}; use crate::query::{query_by_id, query_total_count, ID}; @@ -27,8 +28,8 @@ use crate::utils::extract_value::extract; const BOOLEAN_TRUE: i64 = 1; #[derive(FromRow, Deserialize)] -pub struct ComponentMembers { - pub component_id: String, +pub struct ModelMembers { + pub model_id: String, pub name: String, #[serde(rename = "type")] pub ty: String, @@ -36,7 +37,7 @@ pub struct ComponentMembers { pub created_at: DateTime, } -pub struct ComponentStateObject { +pub struct ModelStateObject { pub name: String, pub type_name: String, pub type_mapping: TypeMapping, @@ -44,7 +45,7 @@ pub struct ComponentStateObject { pub order_input: OrderInputObject, } -impl ComponentStateObject { +impl ModelStateObject { pub fn new(name: String, type_name: String, type_mapping: TypeMapping) -> Self { let where_input = WhereInputObject::new(type_name.as_str(), &type_mapping); let order_input = OrderInputObject::new(type_name.as_str(), &type_mapping); @@ -52,7 +53,7 @@ impl ComponentStateObject { } } -impl ObjectTrait for ComponentStateObject { +impl ObjectTrait for ModelStateObject { fn name(&self) -> &str { &self.name } @@ -61,12 +62,12 @@ impl ObjectTrait for ComponentStateObject { &self.type_name } - // Type mapping contains all component members and their corresponding type + // Type mapping contains all model members and their corresponding type fn type_mapping(&self) -> &TypeMapping { &self.type_mapping } - // Associate component to its parent entity + // Associate model to its parent entity fn nested_fields(&self) -> Option> { Some(vec![entity_field()]) } @@ -83,7 +84,7 @@ impl ObjectTrait for ComponentStateObject { let name = self.name.clone(); let type_mapping = self.type_mapping.clone(); let where_mapping = self.where_input.type_mapping.clone(); - let field_name = format!("{}Components", self.name()); + let field_name = format!("{}Models", self.name()); let field_type = format!("{}Connection", self.type_name()); let mut field = Field::new(field_name, TypeRef::named(field_type), move |ctx| { @@ -98,10 +99,10 @@ impl ObjectTrait for ComponentStateObject { let filters = parse_where_argument(&ctx, &where_mapping)?; let connection = parse_connection_arguments(&ctx)?; let data = - component_states_query(&mut conn, &table_name, &order, &filters, &connection) + model_states_query(&mut conn, &table_name, &order, &filters, &connection) .await?; let total_count = query_total_count(&mut conn, &table_name, &filters).await?; - let connection = component_connection(&data, &type_mapping, total_count)?; + let connection = model_connection(&data, &type_mapping, total_count)?; Ok(Some(Value::Object(connection))) }) @@ -134,7 +135,7 @@ fn entity_field() -> Field { }) } -pub async fn component_state_by_id_query( +pub async fn model_state_by_id_query( conn: &mut PoolConnection, name: &str, id: &str, @@ -147,7 +148,7 @@ pub async fn component_state_by_id_query( value_mapping_from_row(&row, fields) } -pub async fn component_states_query( +pub async fn model_states_query( conn: &mut PoolConnection, table_name: &str, order: &Option, @@ -215,14 +216,14 @@ pub async fn component_states_query( sqlx::query(&query).fetch_all(conn).await } -// TODO: make `connection_output()` more generic. Currently, `component_connection()` method +// TODO: make `connection_output()` more generic. Currently, `model_connection()` method // required as we need to explicity add `entity_id` to each edge. -pub fn component_connection( +pub fn model_connection( data: &[SqliteRow], types: &TypeMapping, total_count: i64, ) -> sqlx::Result { - let component_edges = data + let model_edges = data .iter() .map(|row| { // entity_id and created_at used to create cursor @@ -244,7 +245,7 @@ pub fn component_connection( Ok(ValueMapping::from([ (Name::new("totalCount"), Value::from(total_count)), - (Name::new("edges"), Value::List(component_edges?)), + (Name::new("edges"), Value::List(model_edges?)), // TODO: add pageInfo ])) } @@ -281,25 +282,25 @@ fn fetch_boolean(row: &SqliteRow, column_name: &str) -> sqlx::Result { pub async fn type_mapping_query( conn: &mut PoolConnection, - component_id: &str, + model_id: &str, ) -> sqlx::Result { - let component_members: Vec = sqlx::query_as( + let model_members: Vec = sqlx::query_as( r#" SELECT - component_id, + model_id, name, type AS ty, key, created_at - FROM component_members WHERE key == FALSE AND component_id = ? + FROM model_members WHERE model_id = ? "#, ) - .bind(component_id) + .bind(model_id) .fetch_all(conn) .await?; let mut type_mapping = TypeMapping::new(); - for member in component_members { + for member in model_members { type_mapping.insert(Name::new(member.name), TypeRef::named(member.ty)); } diff --git a/crates/torii/graphql/src/object/system.rs b/crates/torii/graphql/src/object/system.rs index cf84ca34ed..0e796f5da5 100644 --- a/crates/torii/graphql/src/object/system.rs +++ b/crates/torii/graphql/src/object/system.rs @@ -27,8 +27,8 @@ pub struct SystemObject { pub type_mapping: TypeMapping, } -impl SystemObject { - pub fn new() -> Self { +impl Default for SystemObject { + fn default() -> Self { Self { type_mapping: IndexMap::from([ (Name::new("id"), TypeRef::named(TypeRef::ID)), @@ -39,7 +39,8 @@ impl SystemObject { ]), } } - +} +impl SystemObject { pub fn value_mapping(system: System) -> ValueMapping { IndexMap::from([ (Name::new("id"), Value::from(system.id)), diff --git a/crates/torii/graphql/src/object/system_call.rs b/crates/torii/graphql/src/object/system_call.rs index 4f0bb4c433..62bd14303f 100644 --- a/crates/torii/graphql/src/object/system_call.rs +++ b/crates/torii/graphql/src/object/system_call.rs @@ -27,8 +27,8 @@ pub struct SystemCallObject { pub type_mapping: TypeMapping, } -impl SystemCallObject { - pub fn new() -> Self { +impl Default for SystemCallObject { + fn default() -> Self { Self { type_mapping: IndexMap::from([ (Name::new("id"), TypeRef::named(TypeRef::ID)), @@ -39,7 +39,8 @@ impl SystemCallObject { ]), } } - +} +impl SystemCallObject { pub fn value_mapping(system_call: SystemCall) -> ValueMapping { IndexMap::from([ (Name::new("id"), Value::from(system_call.id.to_string())), diff --git a/crates/torii/graphql/src/query/filter.rs b/crates/torii/graphql/src/query/filter.rs index 6ece9a9d89..5fd8cde198 100644 --- a/crates/torii/graphql/src/query/filter.rs +++ b/crates/torii/graphql/src/query/filter.rs @@ -49,7 +49,7 @@ pub fn parse_filter(input: &Name, value: FilterValue) -> Filter { for (suffix, comparator) in suffixes { if let Some(field) = input.strip_suffix(suffix) { - // Filtering only applies to component members which are stored in db with + // Filtering only applies to model members which are stored in db with // external_{name} return Filter { field: format!("external_{}", field), diff --git a/crates/torii/graphql/src/route.rs b/crates/torii/graphql/src/route.rs new file mode 100644 index 0000000000..22a650ce10 --- /dev/null +++ b/crates/torii/graphql/src/route.rs @@ -0,0 +1,31 @@ +use std::convert::Infallible; + +use async_graphql::dynamic::Schema; +use async_graphql::http::{playground_source, GraphQLPlaygroundConfig}; +use sqlx::{Pool, Sqlite}; +use warp::Filter; + +use super::schema::build_schema; + +pub async fn filter( + pool: &Pool, +) -> impl Filter + Clone { + let schema = build_schema(pool).await.unwrap(); + let graphql_filter = warp::path("graphql") + .and(warp::post()) + .and(async_graphql_warp::graphql(schema)) + .and_then(|(schema, request): (Schema, async_graphql::Request)| async move { + // Execute query + let response = schema.execute(request).await; + // Return result + Ok::<_, Infallible>(warp::reply::json(&response)) + }); + + let graphiql_filter = warp::path("graphql").and(warp::get()).map(|| { + warp::reply::html(playground_source( + GraphQLPlaygroundConfig::new("/graphql").subscription_endpoint("/graphql/ws"), + )) + }); + + graphql_filter.or(graphiql_filter) +} diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 7725508525..a5c4a2cda7 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -1,38 +1,43 @@ use anyhow::Result; -use async_graphql::dynamic::{Field, Object, Scalar, Schema, Union}; +use async_graphql::dynamic::{ + Field, Object, Scalar, Schema, Subscription, SubscriptionField, Union, +}; use sqlx::SqlitePool; +use torii_core::types::Model; -use super::object::component::Component; -use super::object::component_state::{type_mapping_query, ComponentStateObject}; use super::object::connection::page_info::PageInfoObject; use super::object::entity::EntityObject; use super::object::event::EventObject; +use super::object::model_state::{type_mapping_query, ModelStateObject}; use super::object::system::SystemObject; use super::object::system_call::SystemCallObject; use super::object::ObjectTrait; use super::types::ScalarType; use super::utils::format_name; +use crate::object::model::ModelObject; // The graphql schema is built dynamically at runtime, this is because we won't know the schema of -// the components until runtime. There are however, predefined objects such as entities and +// the models until runtime. There are however, predefined objects such as entities and // system_calls, their schema is known but we generate them dynamically as well since async-graphql // does not allow mixing of static and dynamic schemas. pub async fn build_schema(pool: &SqlitePool) -> Result { - let mut schema_builder = Schema::build("Query", None, None); + let mut schema_builder = Schema::build("Query", None, Some("Subscription")); // predefined objects let mut objects: Vec> = vec![ - Box::new(EntityObject::new()), - Box::new(SystemObject::new()), - Box::new(EventObject::new()), - Box::new(SystemCallObject::new()), - Box::new(PageInfoObject::new()), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), ]; - // register dynamic component objects - let (component_objects, component_union) = component_objects(pool).await?; - objects.extend(component_objects); - schema_builder = schema_builder.register(component_union); + // register dynamic model objects + let (model_objects, model_union) = model_objects(pool).await?; + objects.extend(model_objects); + + schema_builder = schema_builder.register(model_union); // collect resolvers for single and plural queries let mut fields: Vec = Vec::new(); @@ -82,34 +87,54 @@ pub async fn build_schema(pool: &SqlitePool) -> Result { schema_builder = schema_builder.register(object.create()); } - schema_builder.register(query_root).data(pool.clone()).finish().map_err(|e| e.into()) + // collect resolvers for single subscriptions + let mut subscription_fields: Vec = Vec::new(); + for object in &objects { + if let Some(subscriptions) = object.subscriptions() { + for sub in subscriptions { + subscription_fields.push(sub); + } + } + } + + // add field resolvers to subscription root + let mut subscription_root = Subscription::new("Subscription"); + for field in subscription_fields { + subscription_root = subscription_root.field(field); + } + + schema_builder + .register(query_root) + .register(subscription_root) + .data(pool.clone()) + .finish() + .map_err(|e| e.into()) } -async fn component_objects(pool: &SqlitePool) -> Result<(Vec>, Union)> { +async fn model_objects(pool: &SqlitePool) -> Result<(Vec>, Union)> { let mut conn = pool.acquire().await?; let mut objects: Vec> = Vec::new(); - let components: Vec = - sqlx::query_as("SELECT * FROM components").fetch_all(&mut conn).await?; + let models: Vec = sqlx::query_as("SELECT * FROM models").fetch_all(&mut conn).await?; - // component union object - let mut component_union = Union::new("ComponentUnion"); + // model union object + let mut model_union = Union::new("ModelUnion"); - // component state objects - for component_metadata in components { - let field_type_mapping = type_mapping_query(&mut conn, &component_metadata.id).await?; + // model state objects + for model_metadata in models { + let field_type_mapping = type_mapping_query(&mut conn, &model_metadata.id).await?; if !field_type_mapping.is_empty() { - let (name, type_name) = format_name(&component_metadata.name); - let state_object = Box::new(ComponentStateObject::new( + let (name, type_name) = format_name(&model_metadata.name); + let state_object = Box::new(ModelStateObject::new( name.clone(), type_name.clone(), field_type_mapping, )); - component_union = component_union.possible_type(&type_name); + model_union = model_union.possible_type(&type_name); objects.push(state_object); } } - Ok((objects, component_union)) + Ok((objects, model_union)) } diff --git a/crates/torii/graphql/src/server.rs b/crates/torii/graphql/src/server.rs deleted file mode 100644 index 241392780a..0000000000 --- a/crates/torii/graphql/src/server.rs +++ /dev/null @@ -1,23 +0,0 @@ -use async_graphql::http::GraphiQLSource; -use async_graphql_poem::GraphQL; -use poem::listener::TcpListener; -use poem::middleware::Cors; -use poem::web::Html; -use poem::{get, handler, EndpointExt, IntoResponse, Route, Server}; -use sqlx::{Pool, Sqlite}; - -use super::schema::build_schema; - -#[handler] -async fn graphiql() -> impl IntoResponse { - Html(GraphiQLSource::build().endpoint("/").finish()) -} - -pub async fn start(pool: Pool) -> anyhow::Result<()> { - let schema = build_schema(&pool).await?; - - let app = Route::new().at("/", get(graphiql).post(GraphQL::new(schema))).with(Cors::new()); - Server::new(TcpListener::bind("0.0.0.0:8080")).run(app).await?; - - Ok(()) -} diff --git a/crates/torii/graphql/src/tests/common/mod.rs b/crates/torii/graphql/src/tests/common/mod.rs deleted file mode 100644 index d8b4a586b2..0000000000 --- a/crates/torii/graphql/src/tests/common/mod.rs +++ /dev/null @@ -1,136 +0,0 @@ -use camino::Utf8PathBuf; -use serde::Deserialize; -use serde_json::Value; -use sqlx::SqlitePool; -use starknet::core::types::FieldElement; -use torii_core::sql::{Executable, Sql}; -use torii_core::State; - -use crate::schema::build_schema; - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct Connection { - pub total_count: i64, - pub edges: Vec>, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct Edge { - pub node: T, - pub cursor: String, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct Entity { - pub component_names: String, - pub keys: Option>, - pub created_at: Option, -} - -#[derive(Deserialize, Debug)] -pub struct Moves { - pub __typename: String, - pub remaining: u32, - pub entity: Option, -} - -#[derive(Deserialize, Debug)] -pub struct Position { - pub __typename: String, - pub x: u32, - pub y: u32, - pub entity: Option, -} - -pub enum Direction { - Forward, - Backward, -} - -#[allow(dead_code)] -pub async fn run_graphql_query(pool: &SqlitePool, query: &str) -> Value { - let schema = build_schema(pool).await.unwrap(); - let res = schema.execute(query).await; - - assert!(res.errors.is_empty(), "GraphQL query returned errors: {:?}", res.errors); - serde_json::to_value(res.data).expect("Failed to serialize GraphQL response") -} - -pub async fn entity_fixtures(pool: &SqlitePool) { - let manifest = dojo_world::manifest::Manifest::load_from_path( - Utf8PathBuf::from_path_buf("../../../examples/ecs/target/dev/manifest.json".into()) - .unwrap(), - ) - .unwrap(); - - let state = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); - state.load_from_manifest(manifest).await.unwrap(); - - // Set entity with one moves component - // remaining: 10 - let key = vec![FieldElement::ONE]; - let moves_values = vec![FieldElement::from_hex_be("0xa").unwrap()]; - state.set_entity("Moves".to_string(), key, moves_values.clone()).await.unwrap(); - - // Set entity with one position component - // x: 42 - // y: 69 - let key = vec![FieldElement::TWO]; - let position_values = vec![ - FieldElement::from_hex_be("0x2a").unwrap(), - FieldElement::from_hex_be("0x45").unwrap(), - ]; - state.set_entity("Position".to_string(), key, position_values.clone()).await.unwrap(); - - // Set an entity with both moves and position components - // remaining: 1 - // x: 69 - // y: 42 - let key = vec![FieldElement::THREE]; - let moves_values = vec![FieldElement::from_hex_be("0x1").unwrap()]; - let position_values = vec![ - FieldElement::from_hex_be("0x45").unwrap(), - FieldElement::from_hex_be("0x2a").unwrap(), - ]; - state.set_entity("Moves".to_string(), key.clone(), moves_values).await.unwrap(); - state.set_entity("Position".to_string(), key, position_values).await.unwrap(); - - state.execute().await.unwrap(); -} - -pub async fn paginate( - pool: &SqlitePool, - cursor: Option, - direction: Direction, - page_size: usize, -) -> Connection { - let (first_last, before_after) = match direction { - Direction::Forward => ("first", "after"), - Direction::Backward => ("last", "before"), - }; - - let cursor = cursor.map_or(String::new(), |c| format!(", {before_after}: \"{c}\"")); - let query = format!( - " - {{ - entities ({first_last}: {page_size} {cursor}) - {{ - totalCount - edges {{ - cursor - node {{ - componentNames - }} - }} - }} - }} - " - ); - - let value = run_graphql_query(pool, &query).await; - let entities = value.get("entities").ok_or("entities not found").unwrap(); - serde_json::from_value(entities.clone()).unwrap() -} diff --git a/crates/torii/graphql/src/tests/entities_test.rs b/crates/torii/graphql/src/tests/entities_test.rs index 08b00cca4e..6ea97362e9 100644 --- a/crates/torii/graphql/src/tests/entities_test.rs +++ b/crates/torii/graphql/src/tests/entities_test.rs @@ -1,21 +1,42 @@ #[cfg(test)] mod tests { + use dojo_test_utils::migration::prepare_migration; + use dojo_test_utils::sequencer::{ + get_default_test_starknet_config, SequencerConfig, TestSequencer, + }; use sqlx::SqlitePool; + use starknet::providers::jsonrpc::HttpTransport; + use starknet::providers::JsonRpcClient; use starknet_crypto::{poseidon_hash_many, FieldElement}; + use torii_client::contract::world::WorldContractReader; + use torii_core::sql::Sql; - use crate::tests::common::{ - entity_fixtures, paginate, run_graphql_query, Direction, Entity, Moves, Position, + use crate::tests::{ + bootstrap_engine, create_pool, entity_fixtures, paginate, run_graphql_query, Entity, Moves, + Paginate, Position, }; - #[sqlx::test(migrations = "../migrations")] - async fn test_entity(pool: SqlitePool) { - entity_fixtures(&pool).await; + #[tokio::test(flavor = "multi_thread")] + async fn test_entity() { + let pool = create_pool().await; + let db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); + let migration = prepare_migration("../../../examples/ecs/target/dev".into()).unwrap(); + let sequencer = + TestSequencer::start(SequencerConfig::default(), get_default_test_starknet_config()) + .await; + let provider = JsonRpcClient::new(HttpTransport::new(sequencer.url())); + let world = WorldContractReader::new(migration.world_address().unwrap(), &provider); + + let _ = bootstrap_engine(&world, &db, &provider, &migration, &sequencer).await; + + entity_fixtures(&db).await; + let entity_id = poseidon_hash_many(&[FieldElement::ONE]); let query = format!( r#" {{ entity(id: "{:#x}") {{ - componentNames + modelNames }} }} "#, @@ -25,22 +46,25 @@ mod tests { let entity = value.get("entity").ok_or("no entity found").unwrap(); let entity: Entity = serde_json::from_value(entity.clone()).unwrap(); - assert_eq!(entity.component_names, "Moves".to_string()); + assert_eq!(entity.model_names, "Moves".to_string()); } + #[ignore] #[sqlx::test(migrations = "../migrations")] - async fn test_entity_components(pool: SqlitePool) { - entity_fixtures(&pool).await; + async fn test_entity_models(pool: SqlitePool) { + let db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); + entity_fixtures(&db).await; let entity_id = poseidon_hash_many(&[FieldElement::THREE]); let query = format!( r#" {{ entity (id: "{:#x}") {{ - components {{ + models {{ __typename ... on Moves {{ remaining + last_direction }} ... on Position {{ x @@ -55,68 +79,47 @@ mod tests { let value = run_graphql_query(&pool, &query).await; let entity = value.get("entity").ok_or("no entity found").unwrap(); - let components = entity.get("components").ok_or("no components found").unwrap(); - let component_moves: Moves = serde_json::from_value(components[0].clone()).unwrap(); - let component_position: Position = serde_json::from_value(components[1].clone()).unwrap(); - - assert_eq!(component_moves.__typename, "Moves"); - assert_eq!(component_moves.remaining, 1); - assert_eq!(component_position.__typename, "Position"); - assert_eq!(component_position.x, 69); - assert_eq!(component_position.y, 42); + let models = entity.get("models").ok_or("no models found").unwrap(); + let model_moves: Moves = serde_json::from_value(models[0].clone()).unwrap(); + let model_position: Position = serde_json::from_value(models[1].clone()).unwrap(); + + assert_eq!(model_moves.__typename, "Moves"); + assert_eq!(model_moves.remaining, 1); + assert_eq!(model_position.__typename, "Position"); + assert_eq!(model_position.x, 69); + assert_eq!(model_position.y, 42); } #[sqlx::test(migrations = "../migrations")] async fn test_entities_pagination(pool: SqlitePool) { - entity_fixtures(&pool).await; + let db = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); + entity_fixtures(&db).await; let page_size = 2; // Forward pagination - let entities_connection = paginate(&pool, None, Direction::Forward, page_size).await; + let entities_connection = paginate(&pool, None, Paginate::Forward, page_size).await; assert_eq!(entities_connection.total_count, 3); assert_eq!(entities_connection.edges.len(), page_size); let cursor: String = entities_connection.edges[0].cursor.clone(); let next_cursor: String = entities_connection.edges[1].cursor.clone(); - let entities_connection = - paginate(&pool, Some(cursor), Direction::Forward, page_size).await; + let entities_connection = paginate(&pool, Some(cursor), Paginate::Forward, page_size).await; assert_eq!(entities_connection.total_count, 3); assert_eq!(entities_connection.edges.len(), page_size); assert_eq!(entities_connection.edges[0].cursor, next_cursor); // Backward pagination - let entities_connection = paginate(&pool, None, Direction::Backward, page_size).await; + let entities_connection = paginate(&pool, None, Paginate::Backward, page_size).await; assert_eq!(entities_connection.total_count, 3); assert_eq!(entities_connection.edges.len(), page_size); let cursor: String = entities_connection.edges[0].cursor.clone(); let next_cursor: String = entities_connection.edges[1].cursor.clone(); let entities_connection = - paginate(&pool, Some(cursor), Direction::Backward, page_size).await; + paginate(&pool, Some(cursor), Paginate::Backward, page_size).await; assert_eq!(entities_connection.total_count, 3); assert_eq!(entities_connection.edges.len(), page_size); assert_eq!(entities_connection.edges[0].cursor, next_cursor); } - - // FIXME: Enable when `WhereInput` param implemented - // #[sqlx::test(migrations = "../migrations")] - // async fn test_entities_with_component_filters(pool: SqlitePool) { - // entity_fixtures(&pool).await; - - // let query = " - // { - // entities (keys: [\"%%\"], componentName:\"Moves\") { - // keys - // componentNames - // } - // } - // "; - // let value = run_graphql_query(&pool, query).await; - - // let entities = value.get("entities").ok_or("entities not found").unwrap(); - // let entities: Vec = serde_json::from_value(entities.clone()).unwrap(); - // assert_eq!(entities[0].keys.clone().unwrap(), "0x1,"); - // assert_eq!(entities[1].keys.clone().unwrap(), "0x3,"); - // } } diff --git a/crates/torii/graphql/src/tests/events_test.rs b/crates/torii/graphql/src/tests/events_test.rs deleted file mode 100644 index a7decace49..0000000000 --- a/crates/torii/graphql/src/tests/events_test.rs +++ /dev/null @@ -1,57 +0,0 @@ -#[cfg(test)] -mod tests { - use serde::Deserialize; - use sqlx::SqlitePool; - - use crate::tests::common::run_graphql_query; - - #[derive(Deserialize)] - pub struct Event { - pub id: String, - } - - #[sqlx::test(migrations = "../migrations", fixtures("systems", "system_calls", "events"))] - async fn test_event(pool: SqlitePool) { - let _ = pool.acquire().await; - - let query = "{ event(id: \"event_1\") { id } }"; - let value = run_graphql_query(&pool, query).await; - - let event = value.get("event").ok_or("no event found").unwrap(); - let event: Event = serde_json::from_value(event.clone()).unwrap(); - assert_eq!(event.id, "event_1".to_string()); - } - - // #[sqlx::test(migrations = "../migrations", fixtures("systems", "system_calls", "events"))] - // async fn test_event_by_keys(pool: SqlitePool) { - // let _ = pool.acquire().await; - - // let query = "{ events(keys: [\"key_1\", \"key_2\", \"key_3\"]) { edges { node { id keys \ - // data createdAt } } } }"; - // let value = run_graphql_query(&pool, query).await; - // let events = value.get("events").ok_or("no event found").unwrap(); - // let edges = events.get("edges").ok_or("no event found").unwrap(); - // let edges: Vec = serde_json::from_value(edges.clone()).unwrap(); - // let node = edges[0].get("node").ok_or("no event found").unwrap(); - // let event: Event = serde_json::from_value(node.clone()).unwrap(); - // assert_eq!(event.id, "event_1".to_string()); - - // let query = "{ events(keys: [\"key_1\", \"key_2\"]) { edges { node { id keys data \ - // createdAt } } } }"; - // let value = run_graphql_query(&pool, query).await; - // let events = value.get("events").ok_or("no event found").unwrap(); - // let edges = events.get("edges").ok_or("no event found").unwrap(); - // let edges: Vec = serde_json::from_value(edges.clone()).unwrap(); - // assert_eq!(edges.len(), 2); - - // let query = "{ events(keys: [\"key_3\"]) { edges { node { id keys data \ - // createdAt } } } }"; - // let value = run_graphql_query(&pool, query).await; - // let events = value.get("events").ok_or("no event found").unwrap(); - // let edges = events.get("edges").ok_or("no event found").unwrap(); - // let edges: Vec = serde_json::from_value(edges.clone()).unwrap(); - // let node = edges[0].get("node").ok_or("no event found").unwrap(); - // let event: Event = serde_json::from_value(node.clone()).unwrap(); - // assert_eq!(event.id, "event_3".to_string()); - // } -} diff --git a/crates/torii/graphql/src/tests/fixtures/events.sql b/crates/torii/graphql/src/tests/fixtures/events.sql deleted file mode 100644 index 9fb5ab89d1..0000000000 --- a/crates/torii/graphql/src/tests/fixtures/events.sql +++ /dev/null @@ -1,6 +0,0 @@ -INSERT INTO events (id, system_call_id, keys, data) -VALUES ('event_1', 1, 'key_1,key_2,key_3', '0x1,0x2,0x3'); -INSERT INTO events (id, system_call_id, keys, data) -VALUES ('event_2', 2, 'key_1,key_2', '0x1,0x2,0x3'); -INSERT INTO events (id, system_call_id, keys, data) -VALUES ('event_3', 3, 'key_3', '0x1,0x2,0x3'); \ No newline at end of file diff --git a/crates/torii/graphql/src/tests/fixtures/system_calls.sql b/crates/torii/graphql/src/tests/fixtures/system_calls.sql deleted file mode 100644 index b475d36073..0000000000 --- a/crates/torii/graphql/src/tests/fixtures/system_calls.sql +++ /dev/null @@ -1,6 +0,0 @@ -INSERT INTO system_calls (id, system_id, transaction_hash, data) -VALUES (1, 'system_1', '0x0', "0x1,0x2,0x3"); -INSERT INTO system_calls (id, system_id, transaction_hash, data) -VALUES (2, 'system_2', '0x0', "0x1,0x2,0x3"); -INSERT INTO system_calls (id, system_id, transaction_hash, data) -VALUES (3, 'system_3', '0x0', "0x1,0x2,0x3"); \ No newline at end of file diff --git a/crates/torii/graphql/src/tests/fixtures/systems.sql b/crates/torii/graphql/src/tests/fixtures/systems.sql deleted file mode 100644 index 1fc0ae4f9d..0000000000 --- a/crates/torii/graphql/src/tests/fixtures/systems.sql +++ /dev/null @@ -1,6 +0,0 @@ -INSERT INTO systems (id, name, class_hash, transaction_hash) -VALUES ('system_1', 'System1', '0x0', '0x0'); -INSERT INTO systems (id, name, class_hash, transaction_hash) -VALUES ('system_2', 'System2', '0x0', '0x0'); -INSERT INTO systems (id, name, class_hash, transaction_hash) -VALUES ('system_3', 'System3', '0x0', '0x0'); \ No newline at end of file diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 9f74e7eceb..37b23363a1 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -1,4 +1,204 @@ -mod common; -mod components_test; +use camino::Utf8PathBuf; +use dojo_test_utils::sequencer::TestSequencer; +use dojo_world::migration::strategy::MigrationStrategy; +use scarb_ui::{OutputFormat, Ui, Verbosity}; +use serde::Deserialize; +use serde_json::Value; +use sozo::ops::migration::execute_strategy; +use sqlx::sqlite::SqlitePoolOptions; +use sqlx::SqlitePool; +use starknet::core::types::{BlockId, BlockTag, FieldElement}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use tokio_stream::StreamExt; +use torii_client::contract::world::WorldContractReader; +use torii_core::engine::{Engine, EngineConfig, Processors}; +use torii_core::processors::register_model::RegisterModelProcessor; +use torii_core::processors::register_system::RegisterSystemProcessor; +use torii_core::processors::store_set_record::StoreSetRecordProcessor; +use torii_core::sql::{Executable, Sql}; + mod entities_test; -mod events_test; +// mod models_test; +// mod subscription_test; + +use crate::schema::build_schema; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Connection { + pub total_count: i64, + pub edges: Vec>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Edge { + pub node: T, + pub cursor: String, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Entity { + pub model_names: String, + pub keys: Option>, + pub created_at: Option, +} + +#[derive(Deserialize, Debug)] +pub struct Moves { + pub __typename: String, + pub remaining: u32, + pub last_direction: u8, + pub entity: Option, +} + +#[derive(Deserialize, Debug)] +pub struct Position { + pub __typename: String, + pub x: u32, + pub y: u32, + pub entity: Option, +} + +pub enum Paginate { + Forward, + Backward, +} + +#[allow(dead_code)] +pub async fn run_graphql_query(pool: &SqlitePool, query: &str) -> Value { + let schema = build_schema(pool).await.unwrap(); + let res = schema.execute(query).await; + + assert!(res.errors.is_empty(), "GraphQL query returned errors: {:?}", res.errors); + serde_json::to_value(res.data).expect("Failed to serialize GraphQL response") +} + +pub async fn create_pool() -> SqlitePool { + let pool = + SqlitePoolOptions::new().max_connections(5).connect("sqlite::memory:").await.unwrap(); + sqlx::migrate!("../migrations").run(&pool).await.unwrap(); + pool +} + +pub async fn bootstrap_engine<'a>( + world: &'a WorldContractReader<'a, JsonRpcClient>, + db: &'a Sql, + provider: &'a JsonRpcClient, + migration: &MigrationStrategy, + sequencer: &TestSequencer, +) -> Result>, Box> { + let mut account = sequencer.account(); + account.set_block_id(BlockId::Tag(BlockTag::Pending)); + + let manifest = dojo_world::manifest::Manifest::load_from_path( + Utf8PathBuf::from_path_buf("../../../examples/ecs/target/dev/manifest.json".into()) + .unwrap(), + ) + .unwrap(); + + db.load_from_manifest(manifest.clone()).await.unwrap(); + + let ui = Ui::new(Verbosity::Verbose, OutputFormat::Text); + execute_strategy(migration, &account, &ui, None).await.unwrap(); + + let engine = Engine::new( + world, + db, + provider, + Processors { + event: vec![ + Box::new(RegisterModelProcessor), + Box::new(RegisterSystemProcessor), + Box::new(StoreSetRecordProcessor), + ], + ..Processors::default() + }, + EngineConfig::default(), + None, + ); + + let _ = engine.sync_to_head(0).await?; + + Ok(engine) +} + +#[allow(dead_code)] +pub async fn run_graphql_subscription( + pool: &SqlitePool, + subscription: &str, +) -> async_graphql::Value { + // Build dynamic schema + let schema = build_schema(pool).await.unwrap(); + schema.execute_stream(subscription).next().await.unwrap().into_result().unwrap().data + // fn subscribe() is called from inside dynamic subscription +} + +pub async fn entity_fixtures(db: &Sql) { + // Set entity with one moves model + // remaining: 10, last_direction: 0 + let key = vec![FieldElement::ONE]; + let moves_values = vec![FieldElement::from_hex_be("0xa").unwrap(), FieldElement::ZERO]; + db.set_entity("Moves".to_string(), key, moves_values.clone()).await.unwrap(); + + // Set entity with one position model + // x: 42 + // y: 69 + let key = vec![FieldElement::TWO]; + let position_values = vec![ + FieldElement::from_hex_be("0x2a").unwrap(), + FieldElement::from_hex_be("0x45").unwrap(), + ]; + db.set_entity("Position".to_string(), key, position_values.clone()).await.unwrap(); + + // Set an entity with both moves and position models + // remaining: 1, last_direction: 0 + // x: 69 + // y: 42 + let key = vec![FieldElement::THREE]; + let moves_values = vec![FieldElement::from_hex_be("0x1").unwrap(), FieldElement::ZERO]; + let position_values = vec![ + FieldElement::from_hex_be("0x45").unwrap(), + FieldElement::from_hex_be("0x2a").unwrap(), + ]; + db.set_entity("Moves".to_string(), key.clone(), moves_values).await.unwrap(); + db.set_entity("Position".to_string(), key, position_values).await.unwrap(); + + db.execute().await.unwrap(); +} + +pub async fn paginate( + pool: &SqlitePool, + cursor: Option, + direction: Paginate, + page_size: usize, +) -> Connection { + let (first_last, before_after) = match direction { + Paginate::Forward => ("first", "after"), + Paginate::Backward => ("last", "before"), + }; + + let cursor = cursor.map_or(String::new(), |c| format!(", {before_after}: \"{c}\"")); + let query = format!( + " + {{ + entities ({first_last}: {page_size} {cursor}) + {{ + totalCount + edges {{ + cursor + node {{ + modelNames + }} + }} + }} + }} + " + ); + + let value = run_graphql_query(pool, &query).await; + let entities = value.get("entities").ok_or("entities not found").unwrap(); + serde_json::from_value(entities.clone()).unwrap() +} diff --git a/crates/torii/graphql/src/tests/components_test.rs b/crates/torii/graphql/src/tests/models_test.rs similarity index 78% rename from crates/torii/graphql/src/tests/components_test.rs rename to crates/torii/graphql/src/tests/models_test.rs index 019383cdff..4d61e5a08d 100644 --- a/crates/torii/graphql/src/tests/components_test.rs +++ b/crates/torii/graphql/src/tests/models_test.rs @@ -2,9 +2,7 @@ mod tests { use sqlx::SqlitePool; - use crate::tests::common::{ - entity_fixtures, run_graphql_query, Connection, Edge, Moves, Position, - }; + use crate::tests::{entity_fixtures, run_graphql_query, Connection, Edge, Moves, Position}; type OrderTestFn = dyn Fn(&Vec>) -> bool; @@ -14,23 +12,25 @@ mod tests { test_order: Box, } + #[ignore] #[sqlx::test(migrations = "../migrations")] - async fn test_component_no_filter(pool: SqlitePool) { + async fn test_model_no_filter(pool: SqlitePool) { entity_fixtures(&pool).await; let query = r#" { - movesComponents { + movesModels { totalCount edges { node { __typename remaining + last_direction } cursor } } - positionComponents { + positionModels { totalCount edges { node { @@ -46,25 +46,25 @@ mod tests { let value = run_graphql_query(&pool, query).await; - let moves_components = value.get("movesComponents").ok_or("no moves found").unwrap(); + let moves_mdoels = value.get("movesModels").ok_or("no moves found").unwrap(); let moves_connection: Connection = - serde_json::from_value(moves_components.clone()).unwrap(); + serde_json::from_value(moves_mdoels.clone()).unwrap(); - let position_components = - value.get("positionComponents").ok_or("no position found").unwrap(); + let position_mdoels = value.get("positionModels").ok_or("no position found").unwrap(); let position_connection: Connection = - serde_json::from_value(position_components.clone()).unwrap(); + serde_json::from_value(position_mdoels.clone()).unwrap(); assert_eq!(moves_connection.edges[0].node.remaining, 10); assert_eq!(position_connection.edges[0].node.x, 42); assert_eq!(position_connection.edges[0].node.y, 69); } + #[ignore] #[sqlx::test(migrations = "../migrations")] - async fn test_component_where_filter(pool: SqlitePool) { + async fn test_model_where_filter(pool: SqlitePool) { entity_fixtures(&pool).await; - // fixtures inserts two position components with members (x: 42, y: 69) and (x: 69, y: 42) + // fixtures inserts two position mdoels with members (x: 42, y: 69) and (x: 69, y: 42) // the following filters and expected total results can be simply calculated let where_filters = Vec::from([ ("where: { x: 42 }", 1), @@ -74,13 +74,14 @@ mod tests { ("where: { xLT: 42 }", 0), ("where: { xLTE: 42 }", 1), ("where: { x: 1337, yGTE: 1234 }", 0), + (r#"where: { player: "0x2" }"#, 1), // player is a key ]); for (filter, expected_total) in where_filters { let query = format!( r#" {{ - positionComponents ({}) {{ + positionModels ({}) {{ totalCount edges {{ node {{ @@ -97,15 +98,16 @@ mod tests { ); let value = run_graphql_query(&pool, &query).await; - let positions = value.get("positionComponents").ok_or("no positions found").unwrap(); + let positions = value.get("positionModels").ok_or("no positions found").unwrap(); let connection: Connection = serde_json::from_value(positions.clone()).unwrap(); assert_eq!(connection.total_count, expected_total); } } + #[ignore] #[sqlx::test(migrations = "../migrations")] - async fn test_component_ordering(pool: SqlitePool) { + async fn test_model_ordering(pool: SqlitePool) { entity_fixtures(&pool).await; let orders: Vec = vec![ @@ -143,7 +145,7 @@ mod tests { let query = format!( r#" {{ - positionComponents (order: {{ direction: {}, field: {} }}) {{ + positionModels (order: {{ direction: {}, field: {} }}) {{ totalCount edges {{ node {{ @@ -160,7 +162,7 @@ mod tests { ); let value = run_graphql_query(&pool, &query).await; - let positions = value.get("positionComponents").ok_or("no positions found").unwrap(); + let positions = value.get("positionModels").ok_or("no positions found").unwrap(); let connection: Connection = serde_json::from_value(positions.clone()).unwrap(); assert_eq!(connection.total_count, 2); @@ -168,13 +170,14 @@ mod tests { } } + #[ignore] #[sqlx::test(migrations = "../migrations")] - async fn test_component_entity_relationship(pool: SqlitePool) { + async fn test_model_entity_relationship(pool: SqlitePool) { entity_fixtures(&pool).await; let query = r#" { - positionComponents { + positionModels { totalCount edges { node { @@ -183,7 +186,7 @@ mod tests { y entity { keys - componentNames + modelNames } } cursor @@ -193,9 +196,9 @@ mod tests { "#; let value = run_graphql_query(&pool, query).await; - let positions = value.get("positionComponents").ok_or("no positions found").unwrap(); + let positions = value.get("positionModels").ok_or("no positions found").unwrap(); let connection: Connection = serde_json::from_value(positions.clone()).unwrap(); let entity = connection.edges[0].node.entity.as_ref().unwrap(); - assert_eq!(entity.component_names, "Position".to_string()); + assert_eq!(entity.model_names, "Position".to_string()); } } diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs new file mode 100644 index 0000000000..3673ea21cd --- /dev/null +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -0,0 +1,207 @@ +#[cfg(test)] +mod tests { + use std::time::Duration; + + use async_graphql::value; + use dojo_types::model::{Member, Struct, Ty}; + use sqlx::SqlitePool; + use starknet_crypto::{poseidon_hash_many, FieldElement}; + use tokio::sync::mpsc; + use tokio_util::sync::CancellationToken; + use torii_core::sql::Sql; + + use crate::tests::{init, run_graphql_subscription}; + + #[sqlx::test(migrations = "../migrations")] + async fn test_entity_subscription(pool: SqlitePool) { + // Sleep in order to run this test in a single thread + tokio::time::sleep(Duration::from_secs(1)).await; + let cts = CancellationToken::new(); + let state = init(cts, &pool).await; + // 0. Preprocess expected entity value + let key = vec![FieldElement::ONE]; + let entity_id = format!("{:#x}", poseidon_hash_many(&key)); + let keys_str = key.iter().map(|k| format!("{:#x}", k)).collect::>().join(","); + let expected_value: async_graphql::Value = value!({ + "entityUpdated": { "id": entity_id.clone(), "keys":vec![keys_str.clone()], "modelNames": "Moves" } + }); + let (tx, mut rx) = mpsc::channel(10); + + tokio::spawn(async move { + // 1. Open process and sleep.Go to execute subscription + tokio::time::sleep(Duration::from_secs(1)).await; + + // Set entity with one moves model + // remaining: 10, last_direction: 0 + let moves_values = vec![FieldElement::from_hex_be("0xa").unwrap(), FieldElement::ZERO]; + state.set_entity("Moves".to_string(), key, moves_values).await.unwrap(); + // 3. fn publish() is called from state.set_entity() + + tx.send(()).await.unwrap(); + }); + + // 2. The subscription is executed and it is listeing, waiting for publish() to be executed + let response_value = run_graphql_subscription( + &pool, + r#" + subscription { + entityUpdated { + id, keys, modelNames + } + }"#, + ) + .await; + // 4. The subcription has received the message from publish() + // 5. Compare values + assert_eq!(expected_value, response_value); + rx.recv().await.unwrap(); + } + + #[sqlx::test(migrations = "../migrations")] + async fn test_entity_subscription_with_id(pool: SqlitePool) { + // Sleep in order to run this test in a single thread + tokio::time::sleep(Duration::from_secs(1)).await; + let cts = CancellationToken::new(); + let state = init(cts, &pool).await; + // 0. Preprocess expected entity value + let key = vec![FieldElement::ONE]; + let entity_id = format!("{:#x}", poseidon_hash_many(&key)); + let keys_str = key.iter().map(|k| format!("{:#x}", k)).collect::>().join(","); + let expected_value: async_graphql::Value = value!({ + "entityUpdated": { "id": entity_id.clone(), "keys":vec![keys_str.clone()], "modelNames": "Moves" } + }); + let (tx, mut rx) = mpsc::channel(10); + + tokio::spawn(async move { + // 1. Open process and sleep.Go to execute subscription + tokio::time::sleep(Duration::from_secs(1)).await; + + // Set entity with one moves model + // remaining: 10, last_direction: 0 + let moves_values = vec![FieldElement::from_hex_be("0xa").unwrap(), FieldElement::ZERO]; + state.set_entity("Moves".to_string(), key, moves_values).await.unwrap(); + // 3. fn publish() is called from state.set_entity() + + tx.send(()).await.unwrap(); + }); + + // 2. The subscription is executed and it is listeing, waiting for publish() to be executed + let response_value = run_graphql_subscription( + &pool, + r#" + subscription { + entityUpdated(id: "0x579e8877c7755365d5ec1ec7d3a94a457eff5d1f40482bbe9729c064cdead2") { + id, keys, modelNames + } + }"#, + ) + .await; + // 4. The subcription has received the message from publish() + // 5. Compare values + assert_eq!(expected_value, response_value); + rx.recv().await.unwrap(); + } + + #[sqlx::test(migrations = "../migrations")] + async fn test_model_subscription(pool: SqlitePool) { + // Sleep in order to run this test at the end in a single thread + tokio::time::sleep(Duration::from_secs(2)).await; + + let state = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); + // 0. Preprocess model value + let name = "Test".to_string(); + let model_id = name.to_lowercase(); + let class_hash = FieldElement::TWO; + let hex_class_hash = format!("{:#x}", class_hash); + let expected_value: async_graphql::Value = value!({ + "modelRegistered": { "id": model_id.clone(), "name":name, "classHash": hex_class_hash } + }); + let (tx, mut rx) = mpsc::channel(7); + + tokio::spawn(async move { + // 1. Open process and sleep.Go to execute subscription + tokio::time::sleep(Duration::from_secs(1)).await; + + let model = Ty::Struct(Struct { + name, + children: vec![Member { + name: "test".into(), + ty: Ty::Name("u32".to_string()), + key: false, + }], + }); + state.register_model(model, vec![], class_hash).await.unwrap(); + + // 3. fn publish() is called from state.set_entity() + + tx.send(()).await.unwrap(); + }); + + // 2. The subscription is executed and it is listeing, waiting for publish() to be executed + let response_value = run_graphql_subscription( + &pool, + r#" + subscription { + modelRegistered { + id, name, classHash + } + }"#, + ) + .await; + // 4. The subcription has received the message from publish() + // 5. Compare values + assert_eq!(expected_value, response_value); + rx.recv().await.unwrap(); + } + + #[sqlx::test(migrations = "../migrations")] + async fn test_model_subscription_with_id(pool: SqlitePool) { + // Sleep in order to run this test at the end in a single thread + tokio::time::sleep(Duration::from_secs(2)).await; + + let state = Sql::new(pool.clone(), FieldElement::ZERO).await.unwrap(); + // 0. Preprocess model value + let name = "Test".to_string(); + let model_id = name.to_lowercase(); + let class_hash = FieldElement::TWO; + let hex_class_hash = format!("{:#x}", class_hash); + let expected_value: async_graphql::Value = value!({ + "modelRegistered": { "id": model_id.clone(), "name":name, "classHash": hex_class_hash } + }); + let (tx, mut rx) = mpsc::channel(7); + + tokio::spawn(async move { + // 1. Open process and sleep.Go to execute subscription + tokio::time::sleep(Duration::from_secs(1)).await; + + let model = Ty::Struct(Struct { + name, + children: vec![Member { + name: "test".into(), + ty: Ty::Name("u32".to_string()), + key: false, + }], + }); + state.register_model(model, vec![], class_hash).await.unwrap(); + // 3. fn publish() is called from state.set_entity() + + tx.send(()).await.unwrap(); + }); + + // 2. The subscription is executed and it is listeing, waiting for publish() to be executed + let response_value = run_graphql_subscription( + &pool, + r#" + subscription { + modelRegistered(id: "test") { + id, name, classHash + } + }"#, + ) + .await; + // 4. The subcription has received the message from publish() + // 5. Compare values + assert_eq!(expected_value, response_value); + rx.recv().await.unwrap(); + } +} diff --git a/crates/torii/graphql/src/types.rs b/crates/torii/graphql/src/types.rs index d355ce1b1f..fe7b22e3ff 100644 --- a/crates/torii/graphql/src/types.rs +++ b/crates/torii/graphql/src/types.rs @@ -19,6 +19,7 @@ pub enum ScalarType { ClassHash, DateTime, Felt252, + Enum, } impl fmt::Display for ScalarType { @@ -37,6 +38,7 @@ impl fmt::Display for ScalarType { ScalarType::ClassHash => write!(f, "ClassHash"), ScalarType::DateTime => write!(f, "DateTime"), ScalarType::Felt252 => write!(f, "felt252"), + ScalarType::Enum => write!(f, "Enum"), } } } @@ -57,6 +59,7 @@ impl ScalarType { ScalarType::ClassHash, ScalarType::DateTime, ScalarType::Felt252, + ScalarType::Enum, ] .into_iter() .collect() @@ -70,6 +73,7 @@ impl ScalarType { ScalarType::U64, ScalarType::USize, ScalarType::Bool, + ScalarType::Enum, ] .into_iter() .collect() @@ -114,6 +118,7 @@ impl FromStr for ScalarType { "ClassHash" => Ok(ScalarType::ClassHash), "DateTime" => Ok(ScalarType::DateTime), "felt252" => Ok(ScalarType::Felt252), + "Enum" => Ok(ScalarType::Enum), _ => Err(anyhow::anyhow!("Unknown type {}", s.to_string())), } } diff --git a/crates/torii/grpc/Cargo.toml b/crates/torii/grpc/Cargo.toml index fc62960334..9b3dbc8030 100644 --- a/crates/torii/grpc/Cargo.toml +++ b/crates/torii/grpc/Cargo.toml @@ -6,9 +6,41 @@ repository.workspace = true version.workspace = true [dependencies] -prost = "0.11" +anyhow.workspace = true +bytes = "1.0" +dojo-types = { path = "../../dojo-types" } +futures.workspace = true +parking_lot.workspace = true +rayon.workspace = true +starknet-crypto.workspace = true +starknet.workspace = true +thiserror.workspace = true + +# server +hyper = "0.14.27" +tonic-web.workspace = true +tower = "0.4.13" +tracing.workspace = true + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tonic-web-wasm-client.workspace = true +wasm-prost.workspace = true +wasm-tonic.workspace = true + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +futures-util = "0.3.28" +prost.workspace = true sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] } -tonic = "0.9" +tokio-stream = "0.1.14" +tokio.workspace = true +tonic.workspace = true +url.workspace = true +warp.workspace = true [build-dependencies] -tonic-build = "0.9" +tonic-build.workspace = true +wasm-tonic-build.workspace = true + +[features] +client = [ ] +server = [ ] # this feature can't be build on wasm32 diff --git a/crates/torii/grpc/build.rs b/crates/torii/grpc/build.rs index 97ef8bcd78..095d64820b 100644 --- a/crates/torii/grpc/build.rs +++ b/crates/torii/grpc/build.rs @@ -1,4 +1,22 @@ fn main() -> Result<(), Box> { - tonic_build::compile_protos("proto/world.proto")?; + let target = std::env::var("TARGET").expect("failed to get TARGET environment variable"); + let feature_client = std::env::var("CARGO_FEATURE_CLIENT"); + let feature_server = std::env::var("CARGO_FEATURE_SERVER"); + + if target.contains("wasm32") { + if feature_server.is_ok() { + panic!("feature `server` is not supported on target `{}`", target); + } + + wasm_tonic_build::configure() + .build_server(false) + .build_client(feature_client.is_ok()) + .compile(&["proto/world.proto"], &["proto"])?; + } else { + tonic_build::configure() + .build_server(feature_server.is_ok()) + .build_client(feature_client.is_ok()) + .compile(&["proto/world.proto"], &["proto"])?; + } Ok(()) } diff --git a/crates/torii/grpc/proto/types.proto b/crates/torii/grpc/proto/types.proto new file mode 100644 index 0000000000..57827fd96f --- /dev/null +++ b/crates/torii/grpc/proto/types.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; +package types; + +message WorldMetadata { + // The hex-encoded address of the world. + string world_address = 1; + // The hex-encoded class hash of the world. + string world_class_hash = 2; + // The hex-encoded address of the executor. + string executor_address = 3; + // The hex-encoded class hash of the executor. + string executor_class_hash = 4; + // A list of metadata for all registered components in the world. + repeated ModelMetadata models = 5; + // A list of metadata for all registered systems in the world. + repeated SystemMetadata systems = 6; +} + +message SystemMetadata { + // System name + string name = 1; + // hex-encoded class hash of the system + string class_hash = 2; +} + +message ModelMetadata { + // Model name + string name = 1; + // Model size + uint32 size = 2; + // hex-encoded class hash of the component + string class_hash = 3; +} + +/// Represents a component for a given entity. +message EntityModel { + /// Model name + string model = 1; + /// Entity keys + repeated string keys = 2; +} + +message StorageEntry { + // The key of the changed value + string key = 1; + // The new value applied to the given address + string value = 2; +} + +message StorageDiff { + // The contract address for which the storage changed + string address = 1; + // The changes in the storage of the contract + repeated StorageEntry storage_entries = 2; +} + +message EntityDiff { + // Storage diffs + repeated StorageDiff storage_diffs = 1; +} + +message EntityUpdate { + string block_hash = 1; + EntityDiff entity_diff = 2; +} + +message PendingEntityUpdate { + EntityDiff entity_diff = 1; +} + +message MaybePendingEntityUpdate { + oneof update { + EntityUpdate entity_update = 1; + PendingEntityUpdate pending_entity_update = 2; + } +} \ No newline at end of file diff --git a/crates/torii/grpc/proto/world.proto b/crates/torii/grpc/proto/world.proto index 720a66d6d8..a170ddaf59 100644 --- a/crates/torii/grpc/proto/world.proto +++ b/crates/torii/grpc/proto/world.proto @@ -1,26 +1,55 @@ syntax = "proto3"; package world; +import "types.proto"; + // The World service provides information about the world. service World { - // Retrieves metadata about the world. - rpc Meta (MetaRequest) returns (MetaReply); + // Retrieves metadata about the World including all the registered components and systems. + rpc WorldMetadata (MetadataRequest) returns (MetadataResponse); + + // rpc ComponentMetadata () returns (); + // rpc SystemMetadata () returns (); + + // Retrieve the component values of the requested entity. + rpc GetEntity (GetEntityRequest) returns (GetEntityResponse); + + /* + * Subscribes to entity updates. + * Bidirectional streaming as we want to allow user to change the list of entities to subscribe to without closing the connection. + */ + rpc SubscribeEntities (SubscribeEntitiesRequest) returns (stream SubscribeEntitiesResponse); } + // A request to retrieve metadata for a specific world ID. -message MetaRequest { - // The address of the world. - string id = 1; +message MetadataRequest { + } // The metadata response contains addresses and class hashes for the world. -message MetaReply { - // The hex-encoded address of the world. - string world_address = 1; - // The hex-encoded class hash of the world. - string world_class_hash = 2; - // The hex-encoded address of the executor. - string executor_address = 3; - // The hex-encoded class hash of the executor. - string executor_class_hash = 4; +message MetadataResponse { + types.WorldMetadata metadata = 1; +} + +// A request to retrieve a component value of an entity. +message GetEntityRequest { + types.EntityModel entity = 1; +} + +// The entity response contains the component values for the requested entities. +message GetEntityResponse { + repeated string values = 1; +} + +message SubscribeEntitiesRequest { + // The address of the World whose entities to subscribe to. + string world = 1; + // The list of entities to subscribe to. + repeated types.EntityModel entities = 2; +} + +message SubscribeEntitiesResponse { + // List of entities that have been updated. + types.MaybePendingEntityUpdate entity_update = 1; } diff --git a/crates/torii/grpc/src/client.rs b/crates/torii/grpc/src/client.rs new file mode 100644 index 0000000000..3c9b4052bf --- /dev/null +++ b/crates/torii/grpc/src/client.rs @@ -0,0 +1,112 @@ +//! Client implementation for the gRPC service. + +use std::str::FromStr; + +use protos::world::{world_client, SubscribeEntitiesRequest}; +use starknet::core::types::FromStrError; +use starknet_crypto::FieldElement; + +use crate::protos::world::{GetEntityRequest, MetadataRequest, SubscribeEntitiesResponse}; +use crate::protos::{self, types}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Grpc(tonic::Status), + #[error("Missing expected data")] + MissingExpectedData, + #[error(transparent)] + Parsing(FromStrError), + + #[cfg(not(target_arch = "wasm32"))] + #[error(transparent)] + Transport(tonic::transport::Error), +} + +/// A lightweight wrapper around the grpc client. +pub struct WorldClient { + world_address: FieldElement, + #[cfg(not(target_arch = "wasm32"))] + inner: world_client::WorldClient, + #[cfg(target_arch = "wasm32")] + inner: world_client::WorldClient, +} + +impl WorldClient { + #[cfg(not(target_arch = "wasm32"))] + pub async fn new(dst: D, world_address: FieldElement) -> Result + where + D: TryInto, + D::Error: Into>, + { + Ok(Self { + world_address, + inner: world_client::WorldClient::connect(dst).await.map_err(Error::Transport)?, + }) + } + + // we make this function async so that we can keep the function signature similar + #[cfg(target_arch = "wasm32")] + pub async fn new(endpoint: String, world_address: FieldElement) -> Result { + Ok(Self { + world_address, + inner: world_client::WorldClient::new(tonic_web_wasm_client::Client::new(endpoint)), + }) + } + + /// Retrieve the metadata of the World. + pub async fn metadata(&mut self) -> Result { + self.inner + .world_metadata(MetadataRequest {}) + .await + .map_err(Error::Grpc) + .and_then(|res| res.into_inner().metadata.ok_or(Error::MissingExpectedData)) + .and_then(|metadata| metadata.try_into().map_err(Error::Parsing)) + } + + /// Retrieves the latest model value of the requested entity keys + pub async fn get_entity( + &mut self, + model: String, + keys: Vec, + ) -> Result, Error> { + let values = self + .inner + .get_entity(GetEntityRequest { + entity: Some(types::EntityModel { + model, + keys: keys.into_iter().map(|k| format!("{k:#x}")).collect(), + }), + }) + .await + .map(|res| res.into_inner().values) + .map_err(Error::Grpc)?; + + values + .iter() + .map(|v| FieldElement::from_str(v)) + .collect::, _>>() + .map_err(Error::Parsing) + } + + /// Subscribe to the state diff for a set of entities of a World. + pub async fn subscribe_entities( + &mut self, + entities: Vec, + ) -> Result, Error> { + self.inner + .subscribe_entities(SubscribeEntitiesRequest { + entities: entities + .into_iter() + .map(|e| protos::types::EntityModel { + model: e.model, + keys: e.keys.into_iter().map(|felt| format!("{felt:#x}")).collect(), + }) + .collect(), + world: format!("{:#x}", self.world_address), + }) + .await + .map_err(Error::Grpc) + .map(|res| res.into_inner()) + } +} diff --git a/crates/torii/grpc/src/conversion.rs b/crates/torii/grpc/src/conversion.rs new file mode 100644 index 0000000000..31d9884443 --- /dev/null +++ b/crates/torii/grpc/src/conversion.rs @@ -0,0 +1,60 @@ +use std::collections::HashMap; +use std::str::FromStr; + +use starknet::core::types::FromStrError; +use starknet_crypto::FieldElement; + +use crate::protos; + +impl TryFrom for dojo_types::model::ModelMetadata { + type Error = FromStrError; + fn try_from(value: protos::types::ModelMetadata) -> Result { + Ok(Self { + name: value.name, + size: value.size, + class_hash: FieldElement::from_str(&value.class_hash)?, + }) + } +} + +impl TryFrom for dojo_types::system::SystemMetadata { + type Error = FromStrError; + fn try_from(value: protos::types::SystemMetadata) -> Result { + Ok(Self { name: value.name, class_hash: FieldElement::from_str(&value.class_hash)? }) + } +} + +impl TryFrom for dojo_types::WorldMetadata { + type Error = FromStrError; + fn try_from(value: protos::types::WorldMetadata) -> Result { + let components = value + .models + .into_iter() + .map(|component| Ok((component.name.clone(), component.try_into()?))) + .collect::, _>>()?; + + let systems = value + .systems + .into_iter() + .map(|system| Ok((system.name.clone(), system.try_into()?))) + .collect::, _>>()?; + + Ok(dojo_types::WorldMetadata { + systems, + components, + world_address: FieldElement::from_str(&value.world_address)?, + world_class_hash: FieldElement::from_str(&value.world_class_hash)?, + executor_address: FieldElement::from_str(&value.executor_address)?, + executor_class_hash: FieldElement::from_str(&value.executor_class_hash)?, + }) + } +} + +impl From for protos::types::EntityModel { + fn from(value: dojo_types::model::EntityModel) -> Self { + Self { + model: value.model, + keys: value.keys.into_iter().map(|key| format!("{key:#}")).collect(), + } + } +} diff --git a/crates/torii/grpc/src/lib.rs b/crates/torii/grpc/src/lib.rs index 74f47ad347..56afad464c 100644 --- a/crates/torii/grpc/src/lib.rs +++ b/crates/torii/grpc/src/lib.rs @@ -1 +1,21 @@ +#[cfg(target_arch = "wasm32")] +extern crate wasm_prost as prost; +#[cfg(target_arch = "wasm32")] +extern crate wasm_tonic as tonic; + +pub mod conversion; + +#[cfg(feature = "client")] +pub mod client; + +#[cfg(feature = "server")] pub mod server; + +pub mod protos { + pub mod world { + tonic::include_proto!("world"); + } + pub mod types { + tonic::include_proto!("types"); + } +} diff --git a/crates/torii/grpc/src/server.rs b/crates/torii/grpc/src/server.rs deleted file mode 100644 index 28f0a10cc4..0000000000 --- a/crates/torii/grpc/src/server.rs +++ /dev/null @@ -1,65 +0,0 @@ -use sqlx::{Pool, Sqlite}; -use tonic::transport::Server; -use tonic::{Request, Response, Status}; -use world::world_server::{World, WorldServer}; -use world::{MetaReply, MetaRequest}; - -pub mod world { - tonic::include_proto!("world"); -} - -#[derive(Debug)] -pub struct DojoWorld { - pool: Pool, -} - -impl DojoWorld { - fn new(pool: Pool) -> Self { - Self { pool } - } -} - -#[tonic::async_trait] -impl World for DojoWorld { - async fn meta( - &self, - request: Request, // Accept request of type MetaRequest - ) -> Result, Status> { - let id = request.into_inner().id; - - let (world_address, world_class_hash, executor_address, executor_class_hash): ( - String, - String, - String, - String, - ) = sqlx::query_as( - "SELECT world_address, world_class_hash, executor_address, executor_class_hash FROM \ - worlds WHERE id = ?", - ) - .bind(id) - .fetch_one(&self.pool) - .await - .map_err(|e| match e { - sqlx::Error::RowNotFound => Status::not_found("World not found"), - _ => Status::internal("Internal error"), - })?; - - let reply = world::MetaReply { - world_address, - world_class_hash, - executor_address, - executor_class_hash, - }; - - Ok(Response::new(reply)) // Send back our formatted greeting - } -} - -pub async fn start(pool: Pool) -> Result<(), Box> { - let addr = "[::1]:50051".parse()?; - let world = DojoWorld::new(pool); - - Server::builder().add_service(WorldServer::new(world)).serve(addr).await?; - - Ok(()) -} diff --git a/crates/torii/grpc/src/server/error.rs b/crates/torii/grpc/src/server/error.rs new file mode 100644 index 0000000000..747dc837c5 --- /dev/null +++ b/crates/torii/grpc/src/server/error.rs @@ -0,0 +1,25 @@ +use starknet::core::types::FromStrError; +use starknet::core::utils::CairoShortStringToFeltError; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider}; + +type JsonRpcClientError = as Provider>::Error; +type ProviderError = starknet::providers::ProviderError; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("parsing error: {0}")] + Parse(#[from] ParseError), + #[error(transparent)] + Provider(#[from] ProviderError), + #[error(transparent)] + Sql(#[from] sqlx::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum ParseError { + #[error(transparent)] + FromStr(#[from] FromStrError), + #[error(transparent)] + CairoShortStringToFelt(#[from] CairoShortStringToFeltError), +} diff --git a/crates/torii/grpc/src/server/logger.rs b/crates/torii/grpc/src/server/logger.rs new file mode 100644 index 0000000000..093a5bb50d --- /dev/null +++ b/crates/torii/grpc/src/server/logger.rs @@ -0,0 +1,49 @@ +use std::task::{Context, Poll}; + +use hyper::Body; +use tonic::body::BoxBody; +use tower::{Layer, Service}; +use tracing::info; + +#[derive(Debug, Clone, Default)] +pub struct Logger { + inner: S, +} + +impl Layer for Logger { + type Service = Logger; + fn layer(&self, inner: S) -> Self::Service { + Logger { inner } + } +} + +impl Service> for Logger +where + S: Service, Response = hyper::Response> + Clone + Send + 'static, + S::Future: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = futures::future::BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: hyper::Request) -> Self::Future { + // This is necessary because tonic internally uses `tower::buffer::Buffer`. + // See https://github.com/tower-rs/tower/issues/547#issuecomment-767629149 + // for details on why this is necessary + let clone = self.inner.clone(); + let mut inner = std::mem::replace(&mut self.inner, clone); + + Box::pin(async move { + // Do extra async work here... + let uri = req.uri().path(); + let method = req.method(); + + info!(target: "grpc", ?method, ?uri); + inner.call(req).await + }) + } +} diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs new file mode 100644 index 0000000000..1ede07d180 --- /dev/null +++ b/crates/torii/grpc/src/server/mod.rs @@ -0,0 +1,255 @@ +pub mod error; +pub mod logger; +pub mod subscription; + +use std::pin::Pin; +use std::str::FromStr; +use std::sync::Arc; + +use futures::Stream; +use protos::world::{ + MetadataRequest, MetadataResponse, SubscribeEntitiesRequest, SubscribeEntitiesResponse, +}; +use sqlx::{Executor, Pool, Row, Sqlite}; +use starknet::core::types::FromStrError; +use starknet::core::utils::cairo_short_string_to_felt; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use starknet_crypto::{poseidon_hash_many, FieldElement}; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{Request, Response, Status}; + +use self::error::Error; +use self::subscription::{EntityModelRequest, EntitySubscriptionService}; +use crate::protos::types::EntityModel; +use crate::protos::world::{GetEntityRequest, GetEntityResponse}; +use crate::protos::{self}; + +#[derive(Debug, Clone)] +pub struct DojoWorld { + world_address: FieldElement, + pool: Pool, + /// Sender<(subscription requests, oneshot sender to send back the response)> + subscription_req_sender: + Sender<(EntityModelRequest, Sender>)>, +} + +impl DojoWorld { + pub fn new( + pool: Pool, + block_rx: Receiver, + world_address: FieldElement, + provider: Arc>, + ) -> Self { + let (subscription_req_sender, rx) = tokio::sync::mpsc::channel(1); + // spawn thread for state update service + tokio::task::spawn(EntitySubscriptionService::new(provider, rx, block_rx)); + Self { pool, subscription_req_sender, world_address } + } +} + +impl DojoWorld { + pub async fn metadata(&self) -> Result { + let (world_address, world_class_hash, executor_address, executor_class_hash): ( + String, + String, + String, + String, + ) = sqlx::query_as(&format!( + "SELECT world_address, world_class_hash, executor_address, executor_class_hash FROM \ + worlds WHERE id = '{:#x}'", + self.world_address + )) + .fetch_one(&self.pool) + .await?; + + let models = sqlx::query_as( + "SELECT c.name, c.class_hash, COUNT(cm.id) FROM models c LEFT JOIN model_members cm \ + ON c.id = cm.model_id GROUP BY c.id", + ) + .fetch_all(&self.pool) + .await? + .into_iter() + .map(|(name, class_hash, size)| protos::types::ModelMetadata { name, class_hash, size }) + .collect::>(); + + let systems = sqlx::query_as("SELECT name, class_hash FROM systems") + .fetch_all(&self.pool) + .await? + .into_iter() + .map(|(name, class_hash)| protos::types::SystemMetadata { name, class_hash }) + .collect::>(); + + Ok(protos::types::WorldMetadata { + systems, + models, + world_address, + world_class_hash, + executor_address, + executor_class_hash, + }) + } + + #[allow(unused)] + pub async fn model_metadata( + &self, + component: String, + ) -> Result { + sqlx::query_as( + "SELECT c.name, c.class_hash, COUNT(cm.id) FROM models c LEFT JOIN model_members cm \ + ON c.id = cm.model_id WHERE c.id = ? GROUP BY c.id", + ) + .bind(component.to_lowercase()) + .fetch_one(&self.pool) + .await + .map(|(name, class_hash, size)| protos::types::ModelMetadata { name, size, class_hash }) + .map_err(Error::from) + } + + #[allow(unused)] + pub async fn system_metadata( + &self, + system: String, + ) -> Result { + sqlx::query_as("SELECT name, class_hash FROM systems WHERE id = ?") + .bind(system.to_lowercase()) + .fetch_one(&self.pool) + .await + .map(|(name, class_hash)| protos::types::SystemMetadata { name, class_hash }) + .map_err(Error::from) + } + + #[allow(unused)] + async fn entity( + &self, + component: String, + entity_keys: Vec, + ) -> Result, Error> { + let entity_id = format!("{:#x}", poseidon_hash_many(&entity_keys)); + // TODO: there's definitely a better way for doing this + self.pool + .fetch_one( + format!( + "SELECT * FROM external_{} WHERE entity_id = '{entity_id}'", + component.to_lowercase() + ) + .as_ref(), + ) + .await + .map_err(Error::from) + .map(|row| { + let size = row.columns().len() - 2; + let mut values = Vec::with_capacity(size); + for (i, _) in row.columns().iter().enumerate().skip(1).take(size) { + let value = match row.try_get::(i) { + Ok(value) => value, + Err(sqlx::Error::ColumnDecode { .. }) => { + row.try_get::(i).expect("decode failed").to_string() + } + Err(e) => panic!("{e}"), + }; + values.push(value); + } + values + }) + } +} + +type ServiceResult = Result, Status>; +type SubscribeEntitiesResponseStream = + Pin> + Send>>; + +#[tonic::async_trait] +impl protos::world::world_server::World for DojoWorld { + async fn world_metadata( + &self, + _request: Request, + ) -> Result, Status> { + let metadata = self.metadata().await.map_err(|e| match e { + Error::Sql(sqlx::Error::RowNotFound) => Status::not_found("World not found"), + e => Status::internal(e.to_string()), + })?; + + Ok(Response::new(MetadataResponse { metadata: Some(metadata) })) + } + + async fn get_entity( + &self, + request: Request, + ) -> Result, Status> { + let GetEntityRequest { entity } = request.into_inner(); + + let Some(EntityModel { model, keys }) = entity else { + return Err(Status::invalid_argument("Entity not specified")); + }; + + let entity_keys = keys + .iter() + .map(|k| FieldElement::from_str(k)) + .collect::, _>>() + .map_err(|e| Status::invalid_argument(format!("Invalid key: {e}")))?; + + let values = self.entity(model, entity_keys).await.map_err(|e| match e { + Error::Sql(sqlx::Error::RowNotFound) => Status::not_found("Entity not found"), + e => Status::internal(e.to_string()), + })?; + + Ok(Response::new(GetEntityResponse { values })) + } + + type SubscribeEntitiesStream = SubscribeEntitiesResponseStream; + + async fn subscribe_entities( + &self, + request: Request, + ) -> ServiceResult { + let SubscribeEntitiesRequest { entities: raw_entities, world } = request.into_inner(); + let (sender, rx) = tokio::sync::mpsc::channel(128); + + let world = FieldElement::from_str(&world) + .map_err(|e| Status::internal(format!("Invalid world address: {e}")))?; + + // in order to be able to compute all the storage address for all the requested entities, we + // need to know the size of the entity component. we can get this information from the + // sql database by querying the component metadata. + + let mut entities = Vec::with_capacity(raw_entities.len()); + for entity in raw_entities { + let keys = entity + .keys + .into_iter() + .map(|v| FieldElement::from_str(&v)) + .collect::, FromStrError>>() + .map_err(|e| Status::internal(format!("parsing error: {e}")))?; + + let model = cairo_short_string_to_felt(&entity.model) + .map_err(|e| Status::internal(format!("parsing error: {e}")))?; + + let (component_len,): (i64,) = + sqlx::query_as("SELECT COUNT(*) FROM model_members WHERE model_id = ?") + .bind(entity.model.to_lowercase()) + .fetch_one(&self.pool) + .await + .map_err(|e| match e { + sqlx::Error::RowNotFound => Status::not_found("Model not found"), + e => Status::internal(e.to_string()), + })?; + + entities.push(self::subscription::Entity { + model: self::subscription::ModelMetadata { + name: model, + len: component_len as usize, + }, + keys, + }) + } + + self.subscription_req_sender + .send((EntityModelRequest { world, entities }, sender)) + .await + .expect("should send subscriber request"); + + Ok(Response::new(Box::pin(ReceiverStream::new(rx)) as Self::SubscribeEntitiesStream)) + } +} diff --git a/crates/torii/grpc/src/server/subscription.rs b/crates/torii/grpc/src/server/subscription.rs new file mode 100644 index 0000000000..d6343d36c1 --- /dev/null +++ b/crates/torii/grpc/src/server/subscription.rs @@ -0,0 +1,286 @@ +//! TODO: move the subscription to a separate file + +use std::collections::{HashSet, VecDeque}; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use futures::Future; +use futures_util::FutureExt; +use protos::types::maybe_pending_entity_update::Update; +use protos::world::SubscribeEntitiesResponse; +use rayon::prelude::*; +use starknet::core::types::{BlockId, ContractStorageDiffItem, MaybePendingStateUpdate}; +use starknet::macros::short_string; +use starknet::providers::{Provider, ProviderError}; +use starknet_crypto::{poseidon_hash_many, FieldElement}; +use tokio::sync::mpsc::{Receiver, Sender}; +use tonic::Status; + +use crate::protos::{self}; + +type GetStateUpdateResult

= + Result::Error>>; +type StateUpdateFuture

= Pin> + Send>>; +type PublishStateUpdateFuture = Pin + Send>>; + +pub struct ModelMetadata { + pub name: FieldElement, + pub len: usize, +} + +pub struct Entity { + pub model: ModelMetadata, + pub keys: Vec, +} + +pub struct EntityModelRequest { + pub world: FieldElement, + pub entities: Vec, +} + +pub struct Subscriber { + /// The world address that the subscriber is interested in. + world: FieldElement, + /// The storage addresses that the subscriber is interested in. + storage_addresses: HashSet, + /// The channel to send the response back to the subscriber. + sender: Sender>, +} + +pub struct SubscriberManager { + /// (set of storage addresses they care about, sender channel to send back the response) + pub subscribers: Vec>, +} + +impl SubscriberManager { + pub fn new() -> Self { + Self { subscribers: Vec::default() } + } + + fn add_subscriber( + &mut self, + request: (EntityModelRequest, Sender>), + ) { + let (EntityModelRequest { world, entities }, sender) = request; + + // convert the list of entites into a list storage addresses + let storage_addresses = entities + .par_iter() + .map(|entity| { + let base = poseidon_hash_many(&[ + short_string!("dojo_storage"), + entity.model.name, + poseidon_hash_many(&entity.keys), + ]); + + (0..entity.model.len) + .into_par_iter() + .map(|i| base + i.into()) + .collect::>() + }) + .flatten() + .collect::>(); + + self.subscribers.push(Arc::new(Subscriber { world, storage_addresses, sender })) + } +} + +impl Default for SubscriberManager { + fn default() -> Self { + Self::new() + } +} + +/// a service which handles entity subscription requests. it is an endless future where it awaits +/// for new blocks, fetch its state update, and publish them to the subscribers. +pub struct EntitySubscriptionService { + /// A channel to communicate with the indexer engine, in order to receive the block number that + /// the indexer engine is processing at any moment. This way, we can sync with the indexer and + /// request the state update of the current block that the indexer is currently processing. + block_rx: Receiver, + /// The Starknet provider. + provider: Arc

, + /// A list of state update futures, each corresponding to a block number that was received from + /// the indexer engine. + state_update_req_futs: VecDeque<(u64, StateUpdateFuture

)>, + + publish_update_fut: Option, + + block_num_queue: Vec, + /// Receive subscribers from gRPC server. + /// This receives streams of (sender channel, list of entities to subscribe) tuple + subscriber_recv: + Receiver<(EntityModelRequest, Sender>)>, + + subscriber_manager: SubscriberManager, +} + +impl

EntitySubscriptionService

+where + P: Provider, +{ + pub fn new( + provider: P, + subscriber_recv: Receiver<( + EntityModelRequest, + Sender>, + )>, + block_rx: Receiver, + ) -> Self { + Self { + block_rx, + subscriber_recv, + provider: Arc::new(provider), + block_num_queue: Default::default(), + publish_update_fut: Default::default(), + state_update_req_futs: Default::default(), + subscriber_manager: SubscriberManager::new(), + } + } + + /// Process the fetched state update, and publish to the subscribers, the relevant values for + /// them. + async fn publish_state_updates_to_subscribers( + subscribers: Vec>, + state_update: MaybePendingStateUpdate, + ) { + let state_diff = match &state_update { + MaybePendingStateUpdate::PendingUpdate(update) => &update.state_diff, + MaybePendingStateUpdate::Update(update) => &update.state_diff, + }; + + // iterate over the list of subscribers, and construct the relevant state diffs for each + // subscriber + for sub in subscribers { + // if there is no state diff for the current world, then skip, otherwise, extract the + // state diffs of the world + let Some(ContractStorageDiffItem { storage_entries: diff_entries, .. }) = + state_diff.storage_diffs.iter().find(|d| d.address == sub.world) + else { + continue; + }; + + let relevant_storage_entries = diff_entries + .iter() + .filter(|entry| sub.storage_addresses.contains(&entry.key)) + .map(|entry| protos::types::StorageEntry { + key: format!("{:#x}", entry.key), + value: format!("{:#x}", entry.value), + }) + .collect::>(); + + // if there is no state diffs relevant to the current subscriber, then skip + if relevant_storage_entries.is_empty() { + continue; + } + + let response = SubscribeEntitiesResponse { + entity_update: Some(protos::types::MaybePendingEntityUpdate { + update: Some(match &state_update { + MaybePendingStateUpdate::PendingUpdate(_) => { + Update::PendingEntityUpdate(protos::types::PendingEntityUpdate { + entity_diff: Some(protos::types::EntityDiff { + storage_diffs: vec![protos::types::StorageDiff { + address: format!("{:#x}", sub.world), + storage_entries: relevant_storage_entries, + }], + }), + }) + } + + MaybePendingStateUpdate::Update(update) => { + Update::EntityUpdate(protos::types::EntityUpdate { + block_hash: format!("{:#x}", update.block_hash), + entity_diff: Some(protos::types::EntityDiff { + storage_diffs: vec![protos::types::StorageDiff { + address: format!("{:#x}", sub.world), + storage_entries: relevant_storage_entries, + }], + }), + }) + } + }), + }), + }; + + match sub.sender.send(Ok(response)).await { + Ok(_) => { + println!("state diff sent") + } + Err(e) => { + println!("stream closed: {e:?}"); + } + } + } + } + + async fn do_get_state_update(provider: Arc

, block_number: u64) -> GetStateUpdateResult

{ + provider.get_state_update(BlockId::Number(block_number)).await + } +} + +// an endless future which will receive the block number from the indexer engine, and will +// request its corresponding state update. +impl

Future for EntitySubscriptionService

+where + P: Provider + Send + Sync + Unpin + 'static, +{ + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pin = self.get_mut(); + + // drain the stream + while let Poll::Ready(Some(block_num)) = pin.block_rx.poll_recv(cx) { + // we still need to drain the stream, even if there are no subscribers. But dont have to + // queue for the block number + if !pin.subscriber_manager.subscribers.is_empty() { + pin.block_num_queue.push(block_num); + } + } + + // if there are any queued block numbers, then fetch the corresponding state updates + while let Some(block_num) = pin.block_num_queue.pop() { + let fut = Box::pin(Self::do_get_state_update(Arc::clone(&pin.provider), block_num)); + pin.state_update_req_futs.push_back((block_num, fut)); + } + + // handle incoming new subscribers + while let Poll::Ready(Some(request)) = pin.subscriber_recv.poll_recv(cx) { + println!("received new subscriber"); + pin.subscriber_manager.add_subscriber(request); + } + + // check if there's ongoing publish future, if yes, poll it and if its still not ready + // then return pending, + // dont request for state update, since we are still waiting for the previous state update + // to be published + if let Some(mut fut) = pin.publish_update_fut.take() { + if fut.poll_unpin(cx).is_pending() { + pin.publish_update_fut = Some(fut); + return Poll::Pending; + } + } + + // poll ongoing state update requests + if let Some((block_num, mut fut)) = pin.state_update_req_futs.pop_front() { + match fut.poll_unpin(cx) { + Poll::Ready(Ok(state_update)) => { + let subscribers = pin.subscriber_manager.subscribers.clone(); + pin.publish_update_fut = Some(Box::pin( + Self::publish_state_updates_to_subscribers(subscribers, state_update), + )); + } + + Poll::Ready(Err(e)) => { + println!("error fetching state update for block {block_num}: {:?}", e) + } + + Poll::Pending => pin.state_update_req_futs.push_back((block_num, fut)), + } + } + + Poll::Pending + } +} diff --git a/crates/torii/migrations/20230316154230_setup.sql b/crates/torii/migrations/20230316154230_setup.sql index 2d2e0b54bf..6d06ca0aef 100644 --- a/crates/torii/migrations/20230316154230_setup.sql +++ b/crates/torii/migrations/20230316154230_setup.sql @@ -13,27 +13,30 @@ CREATE TABLE worlds ( created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE components ( +CREATE TABLE models ( id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL, class_hash TEXT NOT NULL, transaction_hash TEXT, + layout BLOB NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_components_created_at ON components (created_at); +CREATE INDEX idx_models_created_at ON models (created_at); -CREATE TABLE component_members( - id INTEGER PRIMARY KEY AUTOINCREMENT, - component_id TEXT NOT NULL, +CREATE TABLE model_members( + id TEXT NOT NULL, + model_idx INTEGER NOT NULL, + member_idx INTEGER NOT NULL, + model_id TEXT NOT NULL, name TEXT NOT NULL, type TEXT NOT NULL, key BOOLEAN NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (component_id) REFERENCES components(id) + PRIMARY KEY (id, model_idx) FOREIGN KEY (model_id) REFERENCES models(id) UNIQUE (id, member_idx) ); -CREATE INDEX idx_component_members_component_id ON component_members (component_id); +CREATE INDEX idx_model_members_model_id ON model_members (model_id); CREATE TABLE system_calls ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -41,8 +44,9 @@ CREATE TABLE system_calls ( transaction_hash TEXT NOT NULL, system_id TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (system_id) REFERENCES systems(id) -); + FOREIGN KEY (system_id) REFERENCES systems(id), + UNIQUE (transaction_hash) +); CREATE INDEX idx_system_calls_created_at ON system_calls (created_at); @@ -59,22 +63,23 @@ CREATE INDEX idx_systems_created_at ON systems (created_at); CREATE TABLE entities ( id TEXT NOT NULL PRIMARY KEY, keys TEXT, - component_names TEXT, + model_names TEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_entities_keys ON entities (keys); + CREATE INDEX idx_entities_keys_create_on ON entities (keys, created_at); CREATE TABLE events ( id TEXT NOT NULL PRIMARY KEY, - system_call_id INTEGER NOT NULL, keys TEXT NOT NULL, data TEXT NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (system_call_id) REFERENCES system_calls(id) + transaction_hash TEXT, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_events_keys ON events (keys); + CREATE INDEX idx_events_created_at ON events (created_at); \ No newline at end of file diff --git a/crates/torii/server/Cargo.toml b/crates/torii/server/Cargo.toml index c1b768e999..ce7267a860 100644 --- a/crates/torii/server/Cargo.toml +++ b/crates/torii/server/Cargo.toml @@ -7,8 +7,7 @@ version = "0.2.1" [dependencies] anyhow.workspace = true -async-graphql = { version = "5.0.8", features = [ "chrono", "dynamic-schema" ] } -async-graphql-poem = "5.0.8" +async-graphql = { version = "5.0.8", features = [ "chrono", "dynamic-schema", "playground" ] } async-trait.workspace = true base64 = "0.21.2" camino.workspace = true @@ -17,8 +16,11 @@ clap.workspace = true ctrlc = "3.2.5" dojo-types = { path = "../../dojo-types" } dojo-world = { path = "../../dojo-world" } +either = "1.9.0" +http = "0.2.9" +http-body = "0.4.5" +hyper.workspace = true indexmap = "1.9.3" -log = "0.4.17" poem = "1.3.48" scarb.workspace = true serde.workspace = true @@ -26,15 +28,20 @@ serde_json.workspace = true sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "uuid" ] } starknet-crypto.workspace = true starknet.workspace = true -tokio = { version = "1.20.1", features = [ "full" ] } tokio-stream = "0.1.11" tokio-util = "0.7.7" +tokio.workspace = true +tonic-web.workspace = true +tonic.workspace = true +torii-client = { path = "../client" } torii-core = { path = "../core" } torii-graphql = { path = "../graphql" } -torii-grpc = { path = "../grpc" } +torii-grpc = { path = "../grpc", features = [ "server" ] } +tower = "0.4.13" tracing-subscriber.workspace = true tracing.workspace = true -url = "2.2.2" +url.workspace = true +warp.workspace = true [dev-dependencies] camino.workspace = true diff --git a/crates/torii/server/src/cli.rs b/crates/torii/server/src/cli.rs index d29f1e8ea3..2017151a61 100644 --- a/crates/torii/server/src/cli.rs +++ b/crates/torii/server/src/cli.rs @@ -1,5 +1,7 @@ use std::env; +use std::net::SocketAddr; use std::str::FromStr; +use std::sync::Arc; use anyhow::{anyhow, Context}; use camino::Utf8PathBuf; @@ -12,20 +14,17 @@ use starknet::core::types::FieldElement; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use tokio_util::sync::CancellationToken; -use torii_core::processors::register_component::RegisterComponentProcessor; +use torii_client::contract::world::WorldContractReader; +use torii_core::engine::{Engine, EngineConfig, Processors}; +use torii_core::processors::register_model::RegisterModelProcessor; use torii_core::processors::register_system::RegisterSystemProcessor; use torii_core::processors::store_set_record::StoreSetRecordProcessor; use torii_core::sql::Sql; -use torii_core::State; use tracing::error; use tracing_subscriber::fmt; use url::Url; -use crate::engine::Processors; -use crate::indexer::Indexer; - -mod engine; -mod indexer; +mod server; /// Dojo World Indexer #[derive(Parser, Debug)] @@ -46,6 +45,12 @@ struct Args { /// Specify a block to start indexing from, ignored if stored head exists #[arg(short, long, default_value = "0")] start_block: u64, + /// Host address for GraphQL/gRPC endpoints + #[arg(long, default_value = "0.0.0.0")] + host: String, + /// Port number for GraphQL/gRPC endpoints + #[arg(long, default_value = "8080")] + port: u16, } #[tokio::main] @@ -73,46 +78,53 @@ async fn main() -> anyhow::Result<()> { let pool = SqlitePoolOptions::new().max_connections(5).connect(database_url).await?; sqlx::migrate!("../migrations").run(&pool).await?; - let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(&args.rpc).unwrap())); + let provider: Arc<_> = JsonRpcClient::new(HttpTransport::new(Url::parse(&args.rpc)?)).into(); let (manifest, env) = get_manifest_and_env(args.manifest.as_ref()) .with_context(|| "Failed to get manifest file".to_string())?; // Get world address let world_address = get_world_address(&args, &manifest, env.as_ref())?; + let world = WorldContractReader::new(world_address, &provider); - let state = Sql::new(pool.clone(), world_address).await?; - state.load_from_manifest(manifest.clone()).await?; + let db = Sql::new(pool.clone(), world_address).await?; + db.load_from_manifest(manifest.clone()).await?; let processors = Processors { event: vec![ - Box::new(RegisterComponentProcessor), + Box::new(RegisterModelProcessor), Box::new(RegisterSystemProcessor), Box::new(StoreSetRecordProcessor), ], + // transaction: vec![Box::new(StoreSystemCallProcessor)], ..Processors::default() }; - let indexer = - Indexer::new(&state, &provider, processors, manifest, world_address, args.start_block); - let graphql = torii_graphql::server::start(pool.clone()); - let grpc = torii_grpc::server::start(pool); + let (block_sender, block_receiver) = tokio::sync::mpsc::channel(100); + + let engine = Engine::new( + &world, + &db, + &provider, + processors, + EngineConfig { start_block: args.start_block, ..Default::default() }, + Some(block_sender), + ); + + let addr: SocketAddr = format!("{}:{}", args.host, args.port).parse()?; tokio::select! { - res = indexer.start() => { + res = engine.start(cts) => { if let Err(e) = res { - error!("Indexer failed with error: {:?}", e); + error!("Indexer failed with error: {e}"); } } - res = graphql => { + + res = server::spawn_server(&addr, &pool, world_address, block_receiver, Arc::clone(&provider)) => { if let Err(e) = res { - error!("GraphQL server failed with error: {:?}", e); - } - } - rs = grpc => { - if let Err(e) = rs { - error!("GRPC server failed with error: {:?}", e); + error!("Server failed with error: {e}"); } } + _ = tokio::signal::ctrl_c() => { println!("Received Ctrl+C, shutting down"); } diff --git a/crates/torii/server/src/engine.rs b/crates/torii/server/src/engine.rs deleted file mode 100644 index bb62c1d885..0000000000 --- a/crates/torii/server/src/engine.rs +++ /dev/null @@ -1,228 +0,0 @@ -use std::error::Error; -use std::time::Duration; - -use starknet::core::types::{ - BlockId, BlockTag, BlockWithTxs, Event, InvokeTransaction, MaybePendingBlockWithTxs, - MaybePendingTransactionReceipt, Transaction, TransactionReceipt, -}; -use starknet::core::utils::get_selector_from_name; -use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcTransport}; -use starknet::providers::Provider; -use starknet_crypto::FieldElement; -use tokio::time::sleep; -use torii_core::processors::{BlockProcessor, EventProcessor, TransactionProcessor}; -use torii_core::sql::Executable; -use torii_core::State; -use tracing::{error, info, warn}; - -pub struct Processors { - pub block: Vec>>, - pub transaction: Vec>>, - pub event: Vec>>, -} - -impl Default for Processors { - fn default() -> Self { - Self { block: vec![], transaction: vec![], event: vec![] } - } -} - -#[derive(Debug)] -pub struct EngineConfig { - pub block_time: Duration, - pub world_address: FieldElement, - pub start_block: u64, -} - -impl Default for EngineConfig { - fn default() -> Self { - Self { - block_time: Duration::from_secs(1), - world_address: FieldElement::ZERO, - start_block: 0, - } - } -} - -pub struct Engine<'a, S: State + Executable, T: JsonRpcTransport + Sync + Send> { - storage: &'a S, - provider: &'a JsonRpcClient, - processors: Processors, - config: EngineConfig, -} - -impl<'a, S: State + Executable, T: JsonRpcTransport + Sync + Send> Engine<'a, S, T> { - pub fn new( - storage: &'a S, - provider: &'a JsonRpcClient, - processors: Processors, - config: EngineConfig, - ) -> Self { - Self { storage, provider, processors, config } - } - - pub async fn start(&self) -> Result<(), Box> { - let storage_head = self.storage.head().await?; - - let mut current_block_number = match storage_head { - 0 => self.config.start_block, - _ => { - if self.config.start_block != 0 { - warn!("start block ignored, stored head exists and will be used instead"); - } - storage_head - } - }; - - loop { - sleep(self.config.block_time).await; - - let latest_block_with_txs = - match self.provider.get_block_with_txs(BlockId::Tag(BlockTag::Latest)).await { - Ok(block_with_txs) => block_with_txs, - Err(e) => { - error!("getting block: {}", e); - continue; - } - }; - - let latest_block_number = match latest_block_with_txs { - MaybePendingBlockWithTxs::Block(latest_block_with_txs) => { - latest_block_with_txs.block_number - } - _ => continue, - }; - - // Process all blocks from current to latest. - while current_block_number <= latest_block_number { - let block_with_txs = match self - .provider - .get_block_with_txs(BlockId::Number(current_block_number)) - .await - { - Ok(block_with_txs) => block_with_txs, - Err(e) => { - error!("getting block: {}", e); - continue; - } - }; - - self.process(block_with_txs).await?; - - self.storage.set_head(current_block_number).await?; - self.storage.execute().await?; - current_block_number += 1; - } - } - } - - async fn process(&self, block: MaybePendingBlockWithTxs) -> Result<(), Box> { - let block: BlockWithTxs = match block { - MaybePendingBlockWithTxs::Block(block) => block, - _ => return Ok(()), - }; - - process_block(self.storage, self.provider, &self.processors.block, &block).await?; - - for transaction in block.clone().transactions { - let invoke_transaction = match &transaction { - Transaction::Invoke(invoke_transaction) => invoke_transaction, - _ => continue, - }; - - let invoke_transaction = match invoke_transaction { - InvokeTransaction::V1(invoke_transaction) => invoke_transaction, - _ => continue, - }; - - let receipt = match self - .provider - .get_transaction_receipt(invoke_transaction.transaction_hash) - .await - { - Ok(receipt) => receipt, - _ => continue, - }; - - let receipt = match receipt { - MaybePendingTransactionReceipt::Receipt(receipt) => receipt, - _ => continue, - }; - - process_transaction( - self.storage, - self.provider, - &self.processors.transaction, - &block, - &receipt.clone(), - ) - .await?; - - if let TransactionReceipt::Invoke(invoke_receipt) = receipt.clone() { - for event in &invoke_receipt.events { - if event.from_address != self.config.world_address { - info!("event not from world address, skipping"); - continue; - } - - process_event( - self.storage, - self.provider, - &self.processors.event, - &block, - &receipt, - event, - ) - .await?; - } - } - } - - info!("processed block: {}", block.block_number); - - Ok(()) - } -} - -async fn process_block( - storage: &S, - provider: &JsonRpcClient, - processors: &[Box>], - block: &BlockWithTxs, -) -> Result<(), Box> { - for processor in processors { - processor.process(storage, provider, block).await?; - } - Ok(()) -} - -async fn process_transaction( - storage: &S, - provider: &JsonRpcClient, - processors: &[Box>], - block: &BlockWithTxs, - receipt: &TransactionReceipt, -) -> Result<(), Box> { - for processor in processors { - processor.process(storage, provider, block, receipt).await?; - } - - Ok(()) -} - -async fn process_event( - storage: &S, - provider: &JsonRpcClient, - processors: &[Box>], - block: &BlockWithTxs, - receipt: &TransactionReceipt, - event: &Event, -) -> Result<(), Box> { - for processor in processors { - if get_selector_from_name(&processor.event_key())? == event.keys[0] { - processor.process(storage, provider, block, receipt, event).await?; - } - } - - Ok(()) -} diff --git a/crates/torii/server/src/indexer.rs b/crates/torii/server/src/indexer.rs deleted file mode 100644 index c4b8e86c87..0000000000 --- a/crates/torii/server/src/indexer.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::error::Error; - -use dojo_world::manifest::Manifest; -use starknet::providers::jsonrpc::{JsonRpcClient, JsonRpcTransport}; -use starknet_crypto::FieldElement; -use torii_core::sql::Executable; -use torii_core::State; -use tracing::info; - -use crate::engine::{Engine, EngineConfig, Processors}; - -#[allow(dead_code)] -pub struct Indexer<'a, S: State + Executable, T: JsonRpcTransport + Sync + Send> { - storage: &'a S, - provider: &'a JsonRpcClient, - engine: Engine<'a, S, T>, - manifest: Manifest, -} - -impl<'a, S: State + Executable, T: JsonRpcTransport + Sync + Send> Indexer<'a, S, T> { - pub fn new( - storage: &'a S, - provider: &'a JsonRpcClient, - processors: Processors, - manifest: Manifest, - world_address: FieldElement, - start_block: u64, - ) -> Self { - let engine = Engine::new( - storage, - provider, - processors, - EngineConfig { world_address, start_block, ..Default::default() }, - ); - Self { storage, provider, engine, manifest } - } - - pub async fn start(&self) -> Result<(), Box> { - info!("starting indexer"); - self.engine.start().await - } -} diff --git a/crates/torii/server/src/server.rs b/crates/torii/server/src/server.rs new file mode 100644 index 0000000000..b906237d7e --- /dev/null +++ b/crates/torii/server/src/server.rs @@ -0,0 +1,129 @@ +use std::convert::Infallible; +use std::net::SocketAddr; +use std::pin::Pin; +use std::str::FromStr; +use std::sync::Arc; +use std::task::Poll; + +use either::Either; +use hyper::service::{make_service_fn, Service}; +use hyper::Uri; +use sqlx::{Pool, Sqlite}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use starknet_crypto::FieldElement; +use tokio::sync::mpsc::Receiver as BoundedReceiver; +use torii_grpc::protos; +use warp::Filter; + +type Error = Box; + +// TODO: check if there's a nicer way to implement this +pub async fn spawn_server( + addr: &SocketAddr, + pool: &Pool, + world_address: FieldElement, + block_receiver: BoundedReceiver, + provider: Arc>, +) -> anyhow::Result<()> { + let world_server = + torii_grpc::server::DojoWorld::new(pool.clone(), block_receiver, world_address, provider); + + let base_route = warp::path::end() + .and(warp::get()) + .map(|| warp::reply::json(&serde_json::json!({ "success": true }))); + let routes = torii_graphql::route::filter(pool).await.or(base_route); + + let warp = warp::service(routes); + let tonic = tonic_web::enable(protos::world::world_server::WorldServer::new(world_server)); + + hyper::Server::bind(addr) + .serve(make_service_fn(move |_| { + let mut tonic = tonic.clone(); + let mut warp = warp.clone(); + + std::future::ready(Ok::<_, Infallible>(tower::service_fn( + move |mut req: hyper::Request| { + let mut path_iter = req.uri().path().split('/').skip(1); + + // check the base path + match path_iter.next() { + // There's a bug in tonic client where the URI path is not respected in + // `Endpoint`, but this issue doesn't exist if `torii-client` is compiled to + // `wasm32-unknown-unknown`. See: https://github.com/hyperium/tonic/issues/1314 + Some("grpc") => { + let grpc_method = path_iter.collect::>().join("/"); + *req.uri_mut() = + Uri::from_str(&format!("/{grpc_method}")).expect("valid uri"); + + Either::Right({ + let res = tonic.call(req); + Box::pin(async move { + let res = res.await.map(|res| res.map(EitherBody::Right))?; + Ok::<_, Error>(res) + }) + }) + } + + _ => Either::Left({ + let res = warp.call(req); + Box::pin(async move { + let res = res.await.map(|res| res.map(EitherBody::Left))?; + Ok::<_, Error>(res) + }) + }), + } + }, + ))) + })) + .await?; + + Ok(()) +} + +enum EitherBody { + Left(A), + Right(B), +} + +impl http_body::Body for EitherBody +where + A: http_body::Body + Send + Unpin, + B: http_body::Body + Send + Unpin, + A::Error: Into, + B::Error: Into, +{ + type Data = A::Data; + type Error = Box; + + fn is_end_stream(&self) -> bool { + match self { + EitherBody::Left(b) => b.is_end_stream(), + EitherBody::Right(b) => b.is_end_stream(), + } + } + + fn poll_data( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll>> { + match self.get_mut() { + EitherBody::Left(b) => Pin::new(b).poll_data(cx).map(map_option_err), + EitherBody::Right(b) => Pin::new(b).poll_data(cx).map(map_option_err), + } + } + + fn poll_trailers( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll, Self::Error>> { + match self.get_mut() { + EitherBody::Left(b) => Pin::new(b).poll_trailers(cx).map_err(Into::into), + EitherBody::Right(b) => Pin::new(b).poll_trailers(cx).map_err(Into::into), + } + } +} + +fn map_option_err>(err: Option>) -> Option> { + err.map(|e| e.map_err(Into::into)) +} diff --git a/dojoup/dojoup b/dojoup/dojoup index 3d2047a5b2..a0e7e2c871 100755 --- a/dojoup/dojoup +++ b/dojoup/dojoup @@ -76,14 +76,16 @@ main() { if [[ "$DOJOUP_REPO" == "dojoengine/dojo" && -z "$DOJOUP_BRANCH" && -z "$DOJOUP_COMMIT" ]]; then DOJOUP_VERSION=${DOJOUP_VERSION-stable} DOJOUP_TAG=$DOJOUP_VERSION - need_cmd jq # Normalize versions (handle channels, versions without v prefix if [[ "$DOJOUP_VERSION" == "stable" ]]; then - # Fetch the list of releases from the GitHub API and get the first non-prerelease + # Fetch the list of releases from the GitHub API and filter out `prerelease`` releases and `alpha`` releases DOJOUP_TAG=$(curl -s "https://api.github.com/repos/${DOJOUP_REPO}/releases" \ - | jq -r '.[] | select(.prerelease==false) | .tag_name' \ - | grep -v '-' \ + | grep -oE '"tag_name": "[^"]*"|"prerelease": (true|false)' \ + | grep -B1 '"prerelease": false' \ + | grep '"tag_name":' \ + | grep -oE '"v[0-9]*\.[0-9]*\.[0-9]*"' \ + | tr -d '"' \ | head -n 1) DOJOUP_VERSION=$DOJOUP_TAG elif [[ "$DOJOUP_VERSION" == nightly* ]]; then diff --git a/examples/ecs/README.md b/examples/ecs/README.md index 6325278901..2571aeb003 100644 --- a/examples/ecs/README.md +++ b/examples/ecs/README.md @@ -11,20 +11,20 @@ sozo build # Migrate the world sozo migrate -# Get the class hash of the Moves component by name -sozo component get --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves +# Get the class hash of the Moves model by name +sozo model get --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves > 0x2b97f0b24be59ecf4504a27ac2301179be7df44c4c7d9482cd7b36137bc0fa4 -# Get the schema of the Moves component -sozo component schema --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves +# Get the schema of the Moves model +sozo model schema --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves > struct Moves { > remaining: u8 > } -# Get the value of the Moves component for an entity. (in this example, -# 0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 is +# Get the value of the Moves model for an entity. (in this example, +# 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 is # the calling account. -sozo component entity --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 +sozo model entity --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 > 0x0 # The returned value is 0 since we haven't spawned yet. Let's spawn @@ -32,6 +32,6 @@ sozo component entity --world 0x26065106fa319c3981618e7567480a50132f23932226a51c sozo execute --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 spawn # Fetch the updated entity -sozo component entity --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 +sozo model entity --world 0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84 Moves 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 > 0xa -``` \ No newline at end of file +``` diff --git a/examples/ecs/Scarb.toml b/examples/ecs/Scarb.toml index 96db091b0b..0fc9f4616f 100644 --- a/examples/ecs/Scarb.toml +++ b/examples/ecs/Scarb.toml @@ -1,5 +1,5 @@ [package] -cairo-version = "2.1.1" +cairo-version = "2.2.0" name = "dojo_examples" version = "0.2.1" @@ -8,21 +8,10 @@ sierra-replace-ids = true [dependencies] dojo = { path = "../../crates/dojo-core" } -dojo_erc = { path = "../../crates/dojo-erc" } +# dojo_erc = { path = "../../crates/dojo-erc" } [[target.dojo]] -build-external-contracts = [ - "dojo_erc::erc721::components::Balance", - "dojo_erc::erc721::components::OperatorApproval", - "dojo_erc::erc721::components::Owner", - "dojo_erc::erc721::components::TokenApproval", - "dojo_erc::erc721::erc721::ERC721", - "dojo_erc::erc721::systems::erc721_approve", - "dojo_erc::erc721::systems::erc721_burn", - "dojo_erc::erc721::systems::erc721_mint", - "dojo_erc::erc721::systems::erc721_set_approval_for_all", - "dojo_erc::erc721::systems::erc721_transfer_from", -] +build-external-contracts = [] [tool.dojo] initializer_class_hash = "0xbeef" @@ -31,5 +20,5 @@ initializer_class_hash = "0xbeef" rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 -account_address = "0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0" -private_key = "0x0300001800000000300000180000000000030000000000003006001800006600" +account_address = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" +private_key = "0x1800000000300000180000000000030000000000003006001800006600" diff --git a/examples/ecs/src/components.cairo b/examples/ecs/src/components.cairo deleted file mode 100644 index f954541d03..0000000000 --- a/examples/ecs/src/components.cairo +++ /dev/null @@ -1,57 +0,0 @@ -use array::ArrayTrait; -use starknet::ContractAddress; - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Moves { - #[key] - player: ContractAddress, - remaining: u8, -} - -#[derive(Component, Copy, Drop, Serde, SerdeLen)] -struct Position { - #[key] - player: ContractAddress, - x: u32, - y: u32 -} - -trait PositionTrait { - fn is_zero(self: Position) -> bool; - fn is_equal(self: Position, b: Position) -> bool; -} - -impl PositionImpl of PositionTrait { - fn is_zero(self: Position) -> bool { - if self.x - self.y == 0 { - return true; - } - false - } - - fn is_equal(self: Position, b: Position) -> bool { - self.x == b.x && self.y == b.y - } -} - -#[cfg(test)] -mod tests { - use debug::PrintTrait; - use super::{Position, PositionTrait}; - - #[test] - #[available_gas(100000)] - fn test_position_is_zero() { - let player = starknet::contract_address_const::<0x0>(); - assert(PositionTrait::is_zero(Position { player, x: 0, y: 0 }), 'not zero'); - } - - #[test] - #[available_gas(100000)] - fn test_position_is_equal() { - let player = starknet::contract_address_const::<0x0>(); - let position = Position { player, x: 420, y: 0 }; - position.print(); - assert(PositionTrait::is_equal(position, Position { player, x: 420, y: 0 }), 'not equal'); - } -} \ No newline at end of file diff --git a/examples/ecs/src/lib.cairo b/examples/ecs/src/lib.cairo index 98d784c920..b0a80e56b0 100644 --- a/examples/ecs/src/lib.cairo +++ b/examples/ecs/src/lib.cairo @@ -1,2 +1,11 @@ -mod components; -mod systems; +mod models; + +mod systems { + // example with #[system] decorator + mod with_decorator; + + // raw example with #[starknet::contract] decorator + mod raw_contract; +} + +mod utils; diff --git a/examples/ecs/src/models.cairo b/examples/ecs/src/models.cairo new file mode 100644 index 0000000000..df6b9d1155 --- /dev/null +++ b/examples/ecs/src/models.cairo @@ -0,0 +1,127 @@ +use array::ArrayTrait; +use core::debug::PrintTrait; +use starknet::ContractAddress; +use dojo::database::schema::{ + EnumMember, Member, Ty, Struct, SchemaIntrospection, serialize_member, serialize_member_type +}; + +#[derive(Serde, Copy, Drop)] +enum Direction { + None: (), + Left: (), + Right: (), + Up: (), + Down: (), +} + +impl DirectionSchemaIntrospectionImpl of SchemaIntrospection { + #[inline(always)] + fn size() -> usize { + 1 + } + + #[inline(always)] + fn layout(ref layout: Array) { + layout.append(8); + } + + #[inline(always)] + fn ty() -> Ty { + Ty::Enum( + EnumMember { + name: 'Direction', + attrs: array![].span(), + values: array![ + serialize_member_type(@Ty::Simple('None')), + serialize_member_type(@Ty::Simple('Left')), + serialize_member_type(@Ty::Simple('Right')), + serialize_member_type(@Ty::Simple('Up')), + serialize_member_type(@Ty::Simple('Down')) + ] + .span() + } + ) + } +} + +impl DirectionPrintImpl of PrintTrait { + fn print(self: Direction) { + match self { + Direction::None(()) => 0.print(), + Direction::Left(()) => 1.print(), + Direction::Right(()) => 2.print(), + Direction::Up(()) => 3.print(), + Direction::Down(()) => 4.print(), + } + } +} + +impl DirectionIntoFelt252 of Into { + fn into(self: Direction) -> felt252 { + match self { + Direction::None(()) => 0, + Direction::Left(()) => 1, + Direction::Right(()) => 2, + Direction::Up(()) => 3, + Direction::Down(()) => 4, + } + } +} + +#[derive(Model, Copy, Drop, Serde)] +struct Moves { + #[key] + player: ContractAddress, + remaining: u8, + last_direction: Direction +} + +#[derive(Copy, Drop, Serde, Print, Introspect)] +struct Vec2 { + x: u32, + y: u32 +} + +#[derive(Model, Copy, Drop, Print, Serde)] +struct Position { + #[key] + player: ContractAddress, + vec: Vec2, +} + +trait Vec2Trait { + fn is_zero(self: Vec2) -> bool; + fn is_equal(self: Vec2, b: Vec2) -> bool; +} + +impl Vec2Impl of Vec2Trait { + fn is_zero(self: Vec2) -> bool { + if self.x - self.y == 0 { + return true; + } + false + } + + fn is_equal(self: Vec2, b: Vec2) -> bool { + self.x == b.x && self.y == b.y + } +} + +#[cfg(test)] +mod tests { + use debug::PrintTrait; + use super::{Position, Vec2, Vec2Trait}; + + #[test] + #[available_gas(100000)] + fn test_vec_is_zero() { + assert(Vec2Trait::is_zero(Vec2 { x: 0, y: 0 }), 'not zero'); + } + + #[test] + #[available_gas(100000)] + fn test_vec_is_equal() { + let position = Vec2 { x: 420, y: 0 }; + assert(position.is_equal(Vec2 { x: 420, y: 0 }), 'not equal'); + } +} diff --git a/examples/ecs/src/systems.cairo b/examples/ecs/src/systems.cairo deleted file mode 100644 index 9760a582d7..0000000000 --- a/examples/ecs/src/systems.cairo +++ /dev/null @@ -1,143 +0,0 @@ -#[system] -mod spawn { - use array::ArrayTrait; - use box::BoxTrait; - use traits::Into; - use dojo::world::Context; - - use dojo_examples::components::Position; - use dojo_examples::components::Moves; - - fn execute(ctx: Context) { - let position = get !(ctx.world, ctx.origin, (Position)); - set !( - ctx.world, - ( - Moves { - player: ctx.origin, remaining: 10 - }, Position { - player: ctx.origin, x: position.x + 10, y: position.y + 10 - }, - ) - ); - return (); - } -} - -#[system] -mod move { - use starknet::ContractAddress; - use array::ArrayTrait; - use box::BoxTrait; - use traits::Into; - use dojo::world::Context; - - use dojo_examples::components::Position; - use dojo_examples::components::Moves; - - #[derive(Drop, starknet::Event)] - struct Moved { - address: ContractAddress, - direction: Direction - } - - - #[derive(Serde, Copy, Drop)] - enum Direction { - Left: (), - Right: (), - Up: (), - Down: (), - } - - impl DirectionIntoFelt252 of Into { - fn into(self: Direction) -> felt252 { - match self { - Direction::Left(()) => 0, - Direction::Right(()) => 1, - Direction::Up(()) => 2, - Direction::Down(()) => 3, - } - } - } - - fn execute(ctx: Context, direction: Direction) { - let (mut position, mut moves) = get !(ctx.world, ctx.origin, (Position, Moves)); - moves.remaining -= 1; - let next = next_position(position, direction); - set !(ctx.world, (moves, next)); - emit !(ctx.world, Moved { address: ctx.origin, direction }); - return (); - } - - fn next_position(mut position: Position, direction: Direction) -> Position { - match direction { - Direction::Left(()) => { - position.x -= 1; - }, - Direction::Right(()) => { - position.x += 1; - }, - Direction::Up(()) => { - position.y -= 1; - }, - Direction::Down(()) => { - position.y += 1; - }, - }; - - position - } -} - -#[cfg(test)] -mod tests { - use core::traits::Into; - use array::ArrayTrait; - - use dojo::world::IWorldDispatcherTrait; - - use dojo::test_utils::spawn_test_world; - - use dojo_examples::components::position; - use dojo_examples::components::Position; - use dojo_examples::components::moves; - use dojo_examples::components::Moves; - use dojo_examples::systems::spawn; - use dojo_examples::systems::move; - - #[test] - #[available_gas(30000000)] - fn test_move() { - let caller = starknet::contract_address_const::<0x0>(); - - // components - let mut components = array::ArrayTrait::new(); - components.append(position::TEST_CLASS_HASH); - components.append(moves::TEST_CLASS_HASH); - components.append(dojo_erc::erc20::components::balance::TEST_CLASS_HASH); - // systems - let mut systems = array::ArrayTrait::new(); - systems.append(spawn::TEST_CLASS_HASH); - systems.append(move::TEST_CLASS_HASH); - - // deploy executor, world and register components/systems - let world = spawn_test_world(components, systems); - - let spawn_call_data = array::ArrayTrait::new(); - world.execute('spawn', spawn_call_data); - - let mut move_calldata = array::ArrayTrait::new(); - move_calldata.append(move::Direction::Right(()).into()); - world.execute('move', move_calldata); - let mut keys = array::ArrayTrait::new(); - keys.append(caller.into()); - - let moves = world.entity('Moves', keys.span(), 0, dojo::SerdeLen::::len()); - assert(*moves[0] == 9, 'moves is wrong'); - let new_position = world - .entity('Position', keys.span(), 0, dojo::SerdeLen::::len()); - assert(*new_position[0] == 11, 'position x is wrong'); - assert(*new_position[1] == 10, 'position y is wrong'); - } -} diff --git a/examples/ecs/src/systems/raw_contract.cairo b/examples/ecs/src/systems/raw_contract.cairo new file mode 100644 index 0000000000..32f405dec6 --- /dev/null +++ b/examples/ecs/src/systems/raw_contract.cairo @@ -0,0 +1,108 @@ +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; +use dojo_examples::models::{Direction}; + +// trait: specify functions to implement +#[starknet::interface] +trait IPlayerActions { + fn spawn(self: @TContractState, world: IWorldDispatcher); + fn move(self: @TContractState, world: IWorldDispatcher, direction: Direction); +} + +// exact same functionality as examples/ecs/src/systems/with_decorator.cairo +// requires some additional code without using system decorator +#[starknet::contract] +mod player_actions_external { + use starknet::{ContractAddress, get_caller_address}; + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use dojo_examples::models::{Position, Moves, Direction, Vec2}; + use dojo_examples::utils::next_position; + use super::IPlayerActions; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Moved: Moved, + } + + #[derive(Drop, starknet::Event)] + struct Moved { + player: ContractAddress, + direction: Direction + } + + // impl: implement functions specified in trait + #[external(v0)] + impl PlayerActionsImpl of IPlayerActions { + fn spawn(self: @ContractState, world: IWorldDispatcher) { + let player = get_caller_address(); + let position = get!(world, player, (Position)); + set!( + world, + ( + Moves { player, remaining: 10, last_direction: Direction::None(()) }, + Position { player, vec: Vec2 { x: 10, y: 10 } }, + ) + ); + } + + fn move(self: @ContractState, world: IWorldDispatcher, direction: Direction) { + let player = get_caller_address(); + let (mut position, mut moves) = get!(world, player, (Position, Moves)); + moves.remaining -= 1; + moves.last_direction = direction; + let next = next_position(position, direction); + set!(world, (moves, next)); + emit!(world, Moved { player, direction }); + return (); + } + } +} + +#[cfg(test)] +mod tests { + use core::traits::Into; + use array::{ArrayTrait}; + + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + use dojo::test_utils::{spawn_test_world, deploy_contract}; + + use dojo_examples::models::{position, moves}; + use dojo_examples::models::{Position, Moves, Direction, Vec2}; + + use super::{ + IPlayerActionsDispatcher, IPlayerActionsDispatcherTrait, + player_actions_external as player_actions + }; + + #[test] + #[available_gas(30000000)] + fn test_move() { + let caller = starknet::contract_address_const::<0x0>(); + + // models + let mut models = array![position::TEST_CLASS_HASH, moves::TEST_CLASS_HASH,]; + // deploy world with models + let world = spawn_test_world(models); + + // deploy systems contract + let contract_address = deploy_contract(player_actions::TEST_CLASS_HASH, array![].span()); + let player_actions_system = IPlayerActionsDispatcher { contract_address }; + + // System calls + player_actions_system.spawn(world); + player_actions_system.move(world, Direction::Right(())); + + let moves = get!(world, caller, Moves); + let right_dir_felt: felt252 = Direction::Right(()).into(); + + assert(moves.remaining == 9, 'moves is wrong'); + assert(moves.last_direction.into() == right_dir_felt, 'last direction is wrong'); + + let new_position = get!(world, caller, Position); + assert(new_position.vec.x == 11, 'position x is wrong'); + assert(new_position.vec.y == 10, 'position y is wrong'); + } +} diff --git a/examples/ecs/src/systems/with_decorator.cairo b/examples/ecs/src/systems/with_decorator.cairo new file mode 100644 index 0000000000..e7b60e3383 --- /dev/null +++ b/examples/ecs/src/systems/with_decorator.cairo @@ -0,0 +1,103 @@ +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; +use dojo_examples::models::{Position, Moves, Direction}; +use starknet::{ContractAddress, ClassHash}; + +// trait: specify functions to implement +#[starknet::interface] +trait IPlayerActions { + fn spawn(self: @TContractState, world: IWorldDispatcher); + fn move(self: @TContractState, world: IWorldDispatcher, direction: Direction); +} + +#[system] +mod player_actions { + use starknet::{ContractAddress, get_caller_address}; + use dojo_examples::models::{Position, Moves, Direction, Vec2}; + use dojo_examples::utils::next_position; + use super::IPlayerActions; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Moved: Moved, + } + + #[derive(Drop, starknet::Event)] + struct Moved { + player: ContractAddress, + direction: Direction + } + + // impl: implement functions specified in trait + #[external(v0)] + impl PlayerActionsImpl of IPlayerActions { + // ContractState is defined by system decorator expansion + fn spawn(self: @ContractState, world: IWorldDispatcher) { + let player = get_caller_address(); + let position = get!(world, player, (Position)); + set!( + world, + ( + Moves { player, remaining: 10, last_direction: Direction::None(()) }, + Position { + player, vec: Vec2 { x: position.vec.x + 10, y: position.vec.y + 10 } + }, + ) + ); + } + + fn move(self: @ContractState, world: IWorldDispatcher, direction: Direction) { + let player = get_caller_address(); + let (mut position, mut moves) = get!(world, player, (Position, Moves)); + moves.remaining -= 1; + moves.last_direction = direction; + let next = next_position(position, direction); + set!(world, (moves, next)); + emit!(world, Moved { player, direction }); + return (); + } + } +} + +#[cfg(test)] +mod tests { + use core::traits::Into; + use array::{ArrayTrait}; + + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + + use dojo::test_utils::{spawn_test_world, deploy_contract}; + + use dojo_examples::models::{position, moves}; + use dojo_examples::models::{Position, Moves, Direction, Vec2}; + use super::{player_actions, IPlayerActionsDispatcher, IPlayerActionsDispatcherTrait}; + + #[test] + #[available_gas(30000000)] + fn test_move() { + let caller = starknet::contract_address_const::<0x0>(); + + // models + let mut models = array![position::TEST_CLASS_HASH, moves::TEST_CLASS_HASH,]; + // deploy world with models + let world = spawn_test_world(models); + + // deploy systems contract + let contract_address = deploy_contract(player_actions::TEST_CLASS_HASH, array![].span()); + let player_actions_system = IPlayerActionsDispatcher { contract_address }; + + // System calls + player_actions_system.spawn(world); + player_actions_system.move(world, Direction::Right(())); + + let moves = get!(world, caller, Moves); + let right_dir_felt: felt252 = Direction::Right(()).into(); + + assert(moves.remaining == 9, 'moves is wrong'); + assert(moves.last_direction.into() == right_dir_felt, 'last direction is wrong'); + + let new_position = get!(world, caller, Position); + assert(new_position.vec.x == 11, 'position x is wrong'); + assert(new_position.vec.y == 10, 'position y is wrong'); + } +} diff --git a/examples/ecs/src/utils.cairo b/examples/ecs/src/utils.cairo new file mode 100644 index 0000000000..ca5c5714f1 --- /dev/null +++ b/examples/ecs/src/utils.cairo @@ -0,0 +1,23 @@ +use dojo_examples::models::{Position, Direction}; + +fn next_position(mut position: Position, direction: Direction) -> Position { + match direction { + Direction::None(()) => { + return position; + }, + Direction::Left(()) => { + position.vec.x -= 1; + }, + Direction::Right(()) => { + position.vec.x += 1; + }, + Direction::Up(()) => { + position.vec.y -= 1; + }, + Direction::Down(()) => { + position.vec.y += 1; + }, + }; + + position +} diff --git a/examples/rpc/starknet/starknet_getClassAt.hurl b/examples/rpc/starknet/starknet_getClassAt.hurl index f53b6d0e46..1cf31f3d04 100644 --- a/examples/rpc/starknet/starknet_getClassAt.hurl +++ b/examples/rpc/starknet/starknet_getClassAt.hurl @@ -5,7 +5,7 @@ Content-Type: application/json "method": "starknet_getClassAt", "params": [ "latest", - "0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0" + "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" ], "id":1 } diff --git a/examples/rpc/starknet/starknet_getClassHashAt.hurl b/examples/rpc/starknet/starknet_getClassHashAt.hurl index 0b6e6188fb..630b8e7884 100644 --- a/examples/rpc/starknet/starknet_getClassHashAt.hurl +++ b/examples/rpc/starknet/starknet_getClassHashAt.hurl @@ -5,7 +5,7 @@ Content-Type: application/json "method": "starknet_getClassHashAt", "params": [ "pending", - "0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0" + "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" ], "id": 1 } diff --git a/examples/rpc/starknet/starknet_getNonce.hurl b/examples/rpc/starknet/starknet_getNonce.hurl index 62ae6e1fe1..f95bee24e2 100644 --- a/examples/rpc/starknet/starknet_getNonce.hurl +++ b/examples/rpc/starknet/starknet_getNonce.hurl @@ -5,7 +5,7 @@ Content-Type: application/json "method": "starknet_getNonce", "params": [ "latest", - "0x3ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0" + "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" ], "id":1 } diff --git a/packages/core/.gitignore b/packages/core/.gitignore deleted file mode 100644 index 76add878f8..0000000000 --- a/packages/core/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist \ No newline at end of file diff --git a/packages/core/.npmignore b/packages/core/.npmignore deleted file mode 100644 index e30ed30a9d..0000000000 --- a/packages/core/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -* - -!dist/** -!package.json -!readme.md -!changelog.md \ No newline at end of file diff --git a/packages/core/global.d.ts b/packages/core/global.d.ts deleted file mode 100644 index a0f27c3008..0000000000 --- a/packages/core/global.d.ts +++ /dev/null @@ -1 +0,0 @@ -import '@types/jest'; \ No newline at end of file diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js deleted file mode 100644 index 05ca416018..0000000000 --- a/packages/core/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest.config.js -module.exports = { - preset: "ts-jest", - testEnvironment: "node", - }; \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json deleted file mode 100644 index dc32c0006d..0000000000 --- a/packages/core/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "@dojoengine/core", - "version": "0.0.15", - "description": "Dojo engine core providers and types", - "scripts": { - "build": "tsc", - "test": "jest" - }, - "author": "Democritus", - "license": "MIT", - "main": "dist/index.js", - "devDependencies": { - "@types/elliptic": "^6.4.14", - "@types/jest": "^29.5.0", - "@types/mocha": "^10.0.1", - "fetch-mock": "^9.11.0", - "jest": "^29.5.0", - "starknet": "^5.14.1", - "ts-jest": "^29.1.0", - "typescript": "^5.0.3" - } -} diff --git a/packages/core/readme.md b/packages/core/readme.md deleted file mode 100644 index f7a8894f17..0000000000 --- a/packages/core/readme.md +++ /dev/null @@ -1,38 +0,0 @@ -# Dojo Core SDK - -Dojo core package to interact with Dojo worlds. It exposes World interface commands to allow the creation of: - -- World explorers -- World deployers -- Dojo games -- Anaylitics - -### Get started - -``` -pnpm install @dojoengine/core -``` - -### ECS Store - -Simple ECS Scaffolding to use in any project. Uses Zustand for efficient Store. - -### Account - -Starknet.js Account wrapper for quick testing - -### Providers - -Providers that clients can use to connect to and interact with Dojo worlds seamlessly. - -#### RPC Provider - -__todo__ - -#### Websocket Providers - -__todo__ - -#### GraphQL Provider - -__todo__ diff --git a/packages/core/src/constants/index.ts b/packages/core/src/constants/index.ts deleted file mode 100644 index f177a8f573..0000000000 --- a/packages/core/src/constants/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const KATANA_ACCOUNT_1_ADDRESS = "0x06f62894bfd81d2e396ce266b2ad0f21e0668d604e5bb1077337b6d570a54aea" -export const KATANA_ACCOUNT_1_PRIVATEKEY = "0x07230b49615d175307d580c33d6fda61fc7b9aec91df0f5c1a5ebe3b8cbfee02" -export const LOCAL_KATANA = 'http://127.0.0.1:5050'; -export const LOCAL_TORII = 'http://localhost:8080' -export const DOJO_STARTER_WORLD = "0x26065106fa319c3981618e7567480a50132f23932226a51c219ffb8e47daa84" \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts deleted file mode 100644 index c5e46f3fe4..0000000000 --- a/packages/core/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { RPCProvider } from './provider'; -export * from './utils' -export * from './types'; diff --git a/packages/core/src/provider/RPCProvider.ts b/packages/core/src/provider/RPCProvider.ts deleted file mode 100644 index e17ed6116a..0000000000 --- a/packages/core/src/provider/RPCProvider.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { RpcProvider, Account, num, Call, InvokeFunctionResponse } from "starknet"; -import { Provider } from "./provider"; -import { Query, WorldEntryPoints } from "../types"; -import { strTofelt252Felt } from '../utils' -import { LOCAL_KATANA } from '../constants'; - -export class RPCProvider extends Provider { - public provider: RpcProvider; - - constructor(world_address: string, url: string = LOCAL_KATANA) { - super(world_address); - this.provider = new RpcProvider({ - nodeUrl: url, - }); - } - - public async entity(component: string, query: Query, offset: number = 0, length: number = 0): Promise> { - - const call: Call = { - entrypoint: WorldEntryPoints.get, // "entity" - contractAddress: this.getWorldAddress(), - calldata: [ - strTofelt252Felt(component), - query.address_domain, - query.keys.length, - ...query.keys as any, - offset, - length - ] - } - - try { - const response = await this.provider.callContract(call); - - return response.result as unknown as Array; - } catch (error) { - throw error; - } - } - - public async entities(component: string, length: number): Promise> { - - const call: Call = { - entrypoint: WorldEntryPoints.entities, - contractAddress: this.getWorldAddress(), - calldata: [strTofelt252Felt(component), length] - } - - try { - const response = await this.provider.callContract(call); - - return response.result as unknown as Array; - } catch (error) { - throw error; - } - } - - public async component(name: string): Promise { - - const call: Call = { - entrypoint: WorldEntryPoints.component, - contractAddress: this.getWorldAddress(), - calldata: [strTofelt252Felt(name)] - } - - try { - const response = await this.provider.callContract(call); - - return response.result as unknown as bigint; - } catch (error) { - throw error; - } - } - - public async execute(account: Account, system: string, call_data: num.BigNumberish[]): Promise { - - try { - const nonce = await account?.getNonce() - const call = await account?.execute( - { - contractAddress: this.getWorldAddress() || "", - entrypoint: WorldEntryPoints.execute, - calldata: [strTofelt252Felt(system), call_data.length, ...call_data] - }, - undefined, - { - nonce, - maxFee: 0 // TODO: Update - } - ); - return call; - } catch (error) { - throw error; - } - } -} \ No newline at end of file diff --git a/packages/core/src/provider/index.ts b/packages/core/src/provider/index.ts deleted file mode 100644 index ad86036171..0000000000 --- a/packages/core/src/provider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RPCProvider } from "./RPCProvider"; \ No newline at end of file diff --git a/packages/core/src/provider/provider.ts b/packages/core/src/provider/provider.ts deleted file mode 100644 index 57d38f1cb7..0000000000 --- a/packages/core/src/provider/provider.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ICommands, Query } from "../types"; - -export abstract class Provider implements ICommands { - private readonly worldAddress: string; - - constructor(worldAddress: string) { - this.worldAddress = worldAddress; - } - - public abstract entity(component: string, query: Query, offset: number, length: number): Promise>; - - public abstract entities(component: string, length: number): Promise>; - - public getWorldAddress(): string { - return this.worldAddress; - } -} \ No newline at end of file diff --git a/packages/core/src/provider/tests/RPCProvider.test.ts b/packages/core/src/provider/tests/RPCProvider.test.ts deleted file mode 100644 index 17a469a889..0000000000 --- a/packages/core/src/provider/tests/RPCProvider.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { RPCProvider } from '../RPCProvider'; -import { Query, WorldEntryPoints } from '../../types'; -import { DOJO_STARTER_WORLD, LOCAL_KATANA } from '../../constants'; - - -describe('RPCProvider', () => { - - const rpcProvider = new RPCProvider(DOJO_STARTER_WORLD, LOCAL_KATANA); - - it('should call entity and return the response as an array of bigints', async () => { - - const mockResponse = { - result: [BigInt(1), BigInt(1), BigInt(1)], - }; - rpcProvider.entity = jest.fn().mockResolvedValue(mockResponse); - - const component = 'Position'; - const query: Query = { address_domain: '0', keys: [BigInt(1), BigInt(1)] }; - const offset = 0; - const length = 3; - - const result = await rpcProvider.entity(component, query, offset, length); - - console.log(result) - - expect(result).toEqual(mockResponse); - }); -}); \ No newline at end of file diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts deleted file mode 100644 index d33209caa4..0000000000 --- a/packages/core/src/types/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export enum WorldEntryPoints { - get = "entity", - set = "set_entity", - entities = "entities", - execute = "execute", - register_system = "register_system", - register_component = "register_component", - component = "component", - system = "system" -} - -export interface Query { - address_domain: string, - keys: bigint[] -} - -export interface ICommands { - - entity?(component: string, query: Query, offset: number, length: number): Promise>; - entities?(component: string, length: number): Promise>; - execute?(name: bigint, execute_calldata: Array): Promise>; - - register_component?(class_hash: string): Promise; - register_system?(class_hash: string): Promise; - - // views - is_authorized?(system: string, component: string): Promise; - is_account_admin?(): Promise; - - component?(name: string): Promise; - system?(name: string): Promise; - - // add generic world commands - blocktime?(): Promise; - worldAge?(): Promise; -} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts deleted file mode 100644 index 452086be61..0000000000 --- a/packages/core/src/utils/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -export function strTofelt252Felt(str: string): string { - const encoder = new TextEncoder(); - const strB = encoder.encode(str); - return BigInt( - strB.reduce((memo, byte) => { - memo += byte.toString(16) - return memo - }, '0x'), - ).toString() -} - -export function getAllComponentNames(manifest: any): any { - return manifest.components.map((component: any) => component.name); -} - -export function getAllComponentNamesAsFelt(manifest: any): any { - return manifest.components.map((component: any) => strTofelt252Felt(component.name)); -} - -export function getAllSystemNames(manifest: any): any { - return manifest.systems.map((system: any) => system.name); -} - -export function getAllSystemNamesAsFelt(manifest: any): any { - return manifest.systems.map((system: any) => strTofelt252Felt(system.name)); -} - diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json deleted file mode 100644 index 64685bdde1..0000000000 --- a/packages/core/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "module": "commonjs", - "declaration": true, - "outDir": "./dist", - "sourceMap": true, - "noImplicitAny": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true, - "strictNullChecks": true, - "types": [ - "jest" - ], - "esModuleInterop": true - }, - "include": [ - "src/**/*.ts" - ], - "skipLibCheck": true, - "exclude": [ - "node_modules", - "dist", - "**/*.test.ts" - ] -} \ No newline at end of file diff --git a/packages/core/yarn.lock b/packages/core/yarn.lock deleted file mode 100644 index c0ef0dadb7..0000000000 --- a/packages/core/yarn.lock +++ /dev/null @@ -1,2367 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" - integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== - dependencies: - "@babel/highlight" "^7.22.5" - -"@babel/compat-data@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" - integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== - -"@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" - integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.9" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.8" - "@babel/types" "^7.22.5" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.1" - -"@babel/generator@^7.22.7", "@babel/generator@^7.22.9", "@babel/generator@^7.7.2": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" - integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== - dependencies: - "@babel/types" "^7.22.5" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" - integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.5" - browserslist "^4.21.9" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== - -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== - dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" - integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-transforms@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" - integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.5" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" - integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== - -"@babel/helper-validator-option@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" - integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== - -"@babel/helpers@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" - integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== - dependencies: - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.6" - "@babel/types" "^7.22.5" - -"@babel/highlight@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" - integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== - dependencies: - "@babel/helper-validator-identifier" "^7.22.5" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" - integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" - integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" - integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.22.5" - -"@babel/runtime@^7.0.0": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" - integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== - dependencies: - regenerator-runtime "^0.13.11" - -"@babel/template@^7.22.5", "@babel/template@^7.3.3": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": - version "7.22.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" - integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/types" "^7.22.5" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" - integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" - to-fast-properties "^2.0.0" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.2.tgz#bf1d4101347c23e07c029a1b1ae07d550f5cc541" - integrity sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w== - dependencies: - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.6.2" - jest-util "^29.6.2" - slash "^3.0.0" - -"@jest/core@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.2.tgz#6f2d1dbe8aa0265fcd4fb8082ae1952f148209c8" - integrity sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg== - dependencies: - "@jest/console" "^29.6.2" - "@jest/reporters" "^29.6.2" - "@jest/test-result" "^29.6.2" - "@jest/transform" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.5.0" - jest-config "^29.6.2" - jest-haste-map "^29.6.2" - jest-message-util "^29.6.2" - jest-regex-util "^29.4.3" - jest-resolve "^29.6.2" - jest-resolve-dependencies "^29.6.2" - jest-runner "^29.6.2" - jest-runtime "^29.6.2" - jest-snapshot "^29.6.2" - jest-util "^29.6.2" - jest-validate "^29.6.2" - jest-watcher "^29.6.2" - micromatch "^4.0.4" - pretty-format "^29.6.2" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.2.tgz#794c0f769d85e7553439d107d3f43186dc6874a9" - integrity sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q== - dependencies: - "@jest/fake-timers" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/node" "*" - jest-mock "^29.6.2" - -"@jest/expect-utils@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.2.tgz#1b97f290d0185d264dd9fdec7567a14a38a90534" - integrity sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg== - dependencies: - jest-get-type "^29.4.3" - -"@jest/expect@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.2.tgz#5a2ad58bb345165d9ce0a1845bbf873c480a4b28" - integrity sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg== - dependencies: - expect "^29.6.2" - jest-snapshot "^29.6.2" - -"@jest/fake-timers@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.2.tgz#fe9d43c5e4b1b901168fe6f46f861b3e652a2df4" - integrity sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA== - dependencies: - "@jest/types" "^29.6.1" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.6.2" - jest-mock "^29.6.2" - jest-util "^29.6.2" - -"@jest/globals@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.2.tgz#74af81b9249122cc46f1eb25793617eec69bf21a" - integrity sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw== - dependencies: - "@jest/environment" "^29.6.2" - "@jest/expect" "^29.6.2" - "@jest/types" "^29.6.1" - jest-mock "^29.6.2" - -"@jest/reporters@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.2.tgz#524afe1d76da33d31309c2c4a2c8062d0c48780a" - integrity sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.6.2" - "@jest/test-result" "^29.6.2" - "@jest/transform" "^29.6.2" - "@jest/types" "^29.6.1" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.6.2" - jest-util "^29.6.2" - jest-worker "^29.6.2" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" - integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/source-map@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" - integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.2.tgz#fdd11583cd1608e4db3114e8f0cce277bf7a32ed" - integrity sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw== - dependencies: - "@jest/console" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz#585eff07a68dd75225a7eacf319780cb9f6b9bf4" - integrity sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw== - dependencies: - "@jest/test-result" "^29.6.2" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.2" - slash "^3.0.0" - -"@jest/transform@^29.6.2": - version "29.6.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.2.tgz#522901ebbb211af08835bc3bcdf765ab778094e3" - integrity sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.1" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.2" - jest-regex-util "^29.4.3" - jest-util "^29.6.2" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" - integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== - dependencies: - "@jest/schemas" "^29.6.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@noble/curves@~1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932" - integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw== - dependencies: - "@noble/hashes" "1.3.0" - -"@noble/hashes@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" - integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== - -"@noble/hashes@~1.3.0": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" - integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@types/babel__core@^7.1.14": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" - integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" - integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== - dependencies: - "@babel/types" "^7.20.7" - -"@types/bn.js@*": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" - integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== - dependencies: - "@types/node" "*" - -"@types/elliptic@^6.4.14": - version "6.4.14" - resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.14.tgz#7bbaad60567a588c1f08b10893453e6b9b4de48e" - integrity sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ== - dependencies: - "@types/bn.js" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" - integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.5.0": - version "29.5.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.3.tgz#7a35dc0044ffb8b56325c6802a4781a626b05777" - integrity sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/mocha@^10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b" - integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== - -"@types/node@*": - version "20.4.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.8.tgz#b5dda19adaa473a9bf0ab5cbd8f30ec7d43f5c85" - integrity sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg== - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -babel-jest@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.2.tgz#cada0a59e07f5acaeb11cbae7e3ba92aec9c1126" - integrity sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A== - dependencies: - "@jest/transform" "^29.6.2" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.5.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" - integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" - integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== - dependencies: - babel-plugin-jest-hoist "^29.5.0" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserslist@^4.21.9: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== - dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001517: - version "1.0.30001519" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz#3e7b8b8a7077e78b0eb054d69e6edf5c7df35601" - integrity sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg== - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -ci-info@^3.2.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== - -cjs-module-lexer@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" - integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -core-js@^3.0.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.0.tgz#7643d353d899747ab1f8b03d2803b0312a0fb3b6" - integrity sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww== - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@^4.1.0, debug@^4.1.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -dedent@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" - integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== - -electron-to-chromium@^1.4.477: - version "1.4.485" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.485.tgz#fde3ee9ee8112a3414c0dfa545385ad08ec43408" - integrity sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.0.0, expect@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521" - integrity sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA== - dependencies: - "@jest/expect-utils" "^29.6.2" - "@types/node" "*" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.6.2" - jest-message-util "^29.6.2" - jest-util "^29.6.2" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fetch-mock@^9.11.0: - version "9.11.0" - resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" - integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== - dependencies: - "@babel/core" "^7.0.0" - "@babel/runtime" "^7.0.0" - core-js "^3.0.0" - debug "^4.1.1" - glob-to-regexp "^0.4.0" - is-subset "^0.1.1" - lodash.isequal "^4.5.0" - path-to-regexp "^2.2.1" - querystring "^0.2.0" - whatwg-url "^6.5.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-to-regexp@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-subset@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" - integrity sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isomorphic-fetch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" - integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== - dependencies: - node-fetch "^2.6.1" - whatwg-fetch "^3.4.1" - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" - integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" - integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== - dependencies: - execa "^5.0.0" - p-limit "^3.1.0" - -jest-circus@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.2.tgz#1e6ffca60151ac66cad63fce34f443f6b5bb4258" - integrity sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw== - dependencies: - "@jest/environment" "^29.6.2" - "@jest/expect" "^29.6.2" - "@jest/test-result" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.6.2" - jest-matcher-utils "^29.6.2" - jest-message-util "^29.6.2" - jest-runtime "^29.6.2" - jest-snapshot "^29.6.2" - jest-util "^29.6.2" - p-limit "^3.1.0" - pretty-format "^29.6.2" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.2.tgz#edb381763398d1a292cd1b636a98bfa5644b8fda" - integrity sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q== - dependencies: - "@jest/core" "^29.6.2" - "@jest/test-result" "^29.6.2" - "@jest/types" "^29.6.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^29.6.2" - jest-util "^29.6.2" - jest-validate "^29.6.2" - prompts "^2.0.1" - yargs "^17.3.1" - -jest-config@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.2.tgz#c68723f06b31ca5e63030686e604727d406cd7c3" - integrity sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.6.2" - "@jest/types" "^29.6.1" - babel-jest "^29.6.2" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.6.2" - jest-environment-node "^29.6.2" - jest-get-type "^29.4.3" - jest-regex-util "^29.4.3" - jest-resolve "^29.6.2" - jest-runner "^29.6.2" - jest-util "^29.6.2" - jest-validate "^29.6.2" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.6.2" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" - integrity sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.6.2" - -jest-docblock@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" - integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.2.tgz#c9e4b340bcbe838c73adf46b76817b15712d02ce" - integrity sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw== - dependencies: - "@jest/types" "^29.6.1" - chalk "^4.0.0" - jest-get-type "^29.4.3" - jest-util "^29.6.2" - pretty-format "^29.6.2" - -jest-environment-node@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.2.tgz#a9ea2cabff39b08eca14ccb32c8ceb924c8bb1ad" - integrity sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ== - dependencies: - "@jest/environment" "^29.6.2" - "@jest/fake-timers" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/node" "*" - jest-mock "^29.6.2" - jest-util "^29.6.2" - -jest-get-type@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" - integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== - -jest-haste-map@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.2.tgz#298c25ea5255cfad8b723179d4295cf3a50a70d1" - integrity sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA== - dependencies: - "@jest/types" "^29.6.1" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.4.3" - jest-util "^29.6.2" - jest-worker "^29.6.2" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz#e2b307fee78cab091c37858a98c7e1d73cdf5b38" - integrity sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ== - dependencies: - jest-get-type "^29.4.3" - pretty-format "^29.6.2" - -jest-matcher-utils@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535" - integrity sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ== - dependencies: - chalk "^4.0.0" - jest-diff "^29.6.2" - jest-get-type "^29.4.3" - pretty-format "^29.6.2" - -jest-message-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.2.tgz#af7adc2209c552f3f5ae31e77cf0a261f23dc2bb" - integrity sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.1" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.6.2" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.2.tgz#ef9c9b4d38c34a2ad61010a021866dad41ce5e00" - integrity sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg== - dependencies: - "@jest/types" "^29.6.1" - "@types/node" "*" - jest-util "^29.6.2" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" - integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== - -jest-resolve-dependencies@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz#36435269b6672c256bcc85fb384872c134cc4cf2" - integrity sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w== - dependencies: - jest-regex-util "^29.4.3" - jest-snapshot "^29.6.2" - -jest-resolve@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.2.tgz#f18405fe4b50159b7b6d85e81f6a524d22afb838" - integrity sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.2" - jest-pnp-resolver "^1.2.2" - jest-util "^29.6.2" - jest-validate "^29.6.2" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.2.tgz#89e8e32a8fef24781a7c4c49cd1cb6358ac7fc01" - integrity sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w== - dependencies: - "@jest/console" "^29.6.2" - "@jest/environment" "^29.6.2" - "@jest/test-result" "^29.6.2" - "@jest/transform" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.4.3" - jest-environment-node "^29.6.2" - jest-haste-map "^29.6.2" - jest-leak-detector "^29.6.2" - jest-message-util "^29.6.2" - jest-resolve "^29.6.2" - jest-runtime "^29.6.2" - jest-util "^29.6.2" - jest-watcher "^29.6.2" - jest-worker "^29.6.2" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.2.tgz#692f25e387f982e89ab83270e684a9786248e545" - integrity sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg== - dependencies: - "@jest/environment" "^29.6.2" - "@jest/fake-timers" "^29.6.2" - "@jest/globals" "^29.6.2" - "@jest/source-map" "^29.6.0" - "@jest/test-result" "^29.6.2" - "@jest/transform" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.6.2" - jest-message-util "^29.6.2" - jest-mock "^29.6.2" - jest-regex-util "^29.4.3" - jest-resolve "^29.6.2" - jest-snapshot "^29.6.2" - jest-util "^29.6.2" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.2.tgz#9b431b561a83f2bdfe041e1cab8a6becdb01af9c" - integrity sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.6.2" - "@jest/transform" "^29.6.2" - "@jest/types" "^29.6.1" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.6.2" - graceful-fs "^4.2.9" - jest-diff "^29.6.2" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.6.2" - jest-message-util "^29.6.2" - jest-util "^29.6.2" - natural-compare "^1.4.0" - pretty-format "^29.6.2" - semver "^7.5.3" - -jest-util@^29.0.0, jest-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" - integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== - dependencies: - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.2.tgz#25d972af35b2415b83b1373baf1a47bb266c1082" - integrity sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg== - dependencies: - "@jest/types" "^29.6.1" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.4.3" - leven "^3.1.0" - pretty-format "^29.6.2" - -jest-watcher@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.2.tgz#77c224674f0620d9f6643c4cfca186d8893ca088" - integrity sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA== - dependencies: - "@jest/test-result" "^29.6.2" - "@jest/types" "^29.6.1" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.6.2" - string-length "^4.0.1" - -jest-worker@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.2.tgz#682fbc4b6856ad0aa122a5403c6d048b83f3fb44" - integrity sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ== - dependencies: - "@types/node" "*" - jest-util "^29.6.2" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.5.0: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.2.tgz#3bd55b9fd46a161b2edbdf5f1d1bd0d1eab76c42" - integrity sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg== - dependencies: - "@jest/core" "^29.6.2" - "@jest/types" "^29.6.1" - import-local "^3.0.2" - jest-cli "^29.6.2" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== - -lossless-json@^2.0.8: - version "2.0.11" - resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-2.0.11.tgz#3137684c93fd99481c6f99c985efc9c9c5cc76a5" - integrity sha512-BP0vn+NGYvzDielvBZaFain/wgeJ1hTvURCqtKvhr1SCPePdaaTanmmcplrHfEJSJOUql7hk4FHwToNJjWRY3g== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@1.x: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -micro-starknet@~0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/micro-starknet/-/micro-starknet-0.2.3.tgz#ff4e7caf599255d2110e9c57bb483dfaf493ccb3" - integrity sha512-6XBcC+GerlwJSR4iA0VaeXtS2wrayWFcA4PEzrJPMuFmWCaUtuGIq5K/DB5F/XgnL54/zl2Bxo690Lj7mYVA8A== - dependencies: - "@noble/curves" "~1.0.0" - "@noble/hashes" "~1.3.0" - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -node-fetch@^2.6.1: - version "2.6.12" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" - integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== - dependencies: - whatwg-url "^5.0.0" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" - integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pirates@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pretty-format@^29.0.0, pretty-format@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47" - integrity sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg== - dependencies: - "@jest/schemas" "^29.6.0" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -pure-rand@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" - integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== - -querystring@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" - integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== - -resolve@^1.20.0: - version "1.22.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" - integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -starknet@5.12.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/starknet/-/starknet-5.12.0.tgz#cc23d1f02053cd926226083ca11907af4699df8e" - integrity sha512-GKx+RdwOfk9rK/lJMQhQmjbc5BIhzX1kOA3HX21uRuu6UXovgWpTOgUzZXKGyRDA9SS4lAsMoLK99ARpC0bUww== - dependencies: - "@noble/curves" "~1.0.0" - isomorphic-fetch "^3.0.0" - lossless-json "^2.0.8" - micro-starknet "~0.2.1" - pako "^2.0.4" - url-join "^4.0.1" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== - dependencies: - punycode "^2.1.0" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -ts-jest@^29.1.0: - version "29.1.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" - integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "^7.5.3" - yargs-parser "^21.0.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -typescript@^5.0.3: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== - -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -url-join@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - -v8-to-istanbul@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" - integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - -whatwg-fetch@^3.4.1: - version "3.6.17" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz#009bbbfc122b227b74ba1ff31536b3a1a0e0e212" - integrity sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -whatwg-url@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" - integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@^21.0.1, yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/packages/react/.gitignore b/packages/react/.gitignore deleted file mode 100644 index 76add878f8..0000000000 --- a/packages/react/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist \ No newline at end of file diff --git a/packages/react/package.json b/packages/react/package.json deleted file mode 100644 index 2df3b4df97..0000000000 --- a/packages/react/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@dojoengine/react", - "version": "0.0.2", - "description": "Dojo React", - "source": "src/index.ts", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "pondering democritus", - "license": "MIT", - "peerDependencies": { - "react": "^17.0 || ^18.0", - "starknet": "next" - }, - "devDependencies": { - "@types/events": "^3.0.0", - "@types/jest": "^29.5.0", - "@types/react": "^18.0.33", - "events": "^3.3.0", - "jest": "^29.5.0", - "ts-jest": "^29.1.0", - "typescript": "^5.0.3", - "@babel/core": "^7.21.4", - "@babel/preset-env": "^7.21.4", - "@latticexyz/recs": "^1.43.0", - "@types/bn.js": "^5.1.1", - "@types/elliptic": "^6.4.14", - "@types/node": "^18.15.11", - "@types/react-dom": "^18.0.11", - "elliptic": "^6.5.4", - "mobx": "^6.9.0", - "rxjs": "^7.8.1", - "type-fest": "^3.11.1" - } -} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts deleted file mode 100644 index 3b3566e849..0000000000 --- a/packages/react/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./useComponentValue"; -export * from "./useEntityQuery"; -export * from "./useObservableValue"; \ No newline at end of file diff --git a/packages/react/src/useComponentValue.ts b/packages/react/src/useComponentValue.ts deleted file mode 100644 index 90bb46fde6..0000000000 --- a/packages/react/src/useComponentValue.ts +++ /dev/null @@ -1,50 +0,0 @@ -// HACK IMPORT FROM https://github.com/latticexyz/mud - ty Lattice! - -import { - Component, - ComponentValue, - defineQuery, - EntityIndex, - getComponentValue, - Has, - isComponentUpdate, - Metadata, - Schema, -} from "@latticexyz/recs"; -import { useEffect, useState } from "react"; - -export function useComponentValue( - component: Component, - entity: EntityIndex | undefined, - defaultValue: ComponentValue -): ComponentValue; - -export function useComponentValue( - component: Component, - entity: EntityIndex | undefined -): ComponentValue | undefined; - -export function useComponentValue( - component: Component, - entity: EntityIndex | undefined, - defaultValue?: ComponentValue -) { - const [value, setValue] = useState(entity != null ? getComponentValue(component, entity) : undefined); - - useEffect(() => { - // component or entity changed, update state to latest value - setValue(entity != null ? getComponentValue(component, entity) : undefined); - if (entity == null) return; - - const queryResult = defineQuery([Has(component)], { runOnInit: false }); - const subscription = queryResult.update$.subscribe((update: any) => { - if (isComponentUpdate(update, component) && update.entity === entity) { - const [nextValue] = update.value; - setValue(nextValue); - } - }); - return () => subscription.unsubscribe(); - }, [component, entity]); - - return value ?? defaultValue; -} \ No newline at end of file diff --git a/packages/react/src/useEntityQuery.ts b/packages/react/src/useEntityQuery.ts deleted file mode 100644 index 0d966cb0a3..0000000000 --- a/packages/react/src/useEntityQuery.ts +++ /dev/null @@ -1,39 +0,0 @@ -// HACK IMPORT FROM https://github.com/latticexyz/mud - ty Lattice! - -import { defineQuery, QueryFragment } from "@latticexyz/recs"; -import { useEffect, useMemo, useState } from "react"; -import { useDeepMemo } from "./utils/useDeepMemo"; -import isEqual from "fast-deep-equal"; -import { distinctUntilChanged, map } from "rxjs"; - -// This does a little more rendering than is necessary when arguments change, -// but at least it's giving correct results now. Will optimize later! - -/** - * Returns all matching entities for a given entity query, - * and triggers a re-render as new query results come in. - * - * @param fragments Query fragments to match against, executed from left to right. - * @param options.updateOnValueChange False - re-renders only on entity array changes. True (default) - also on component value changes. - * @returns Set of entities matching the query fragments. - */ -export function useEntityQuery(fragments: QueryFragment[], options?: { updateOnValueChange?: boolean }) { - const updateOnValueChange = options?.updateOnValueChange ?? true; - - const stableFragments = useDeepMemo(fragments); - const query = useMemo(() => defineQuery(stableFragments, { runOnInit: true }), [stableFragments]); - const [entities, setEntities] = useState([...query.matching]); - - useEffect(() => { - setEntities([...query.matching]); - let observable = query.update$.pipe(map(() => [...query.matching])); - if (!updateOnValueChange) { - // re-render only on entity array changes - observable = observable.pipe(distinctUntilChanged((a, b) => isEqual(a, b))); - } - const subscription = observable.subscribe((entities) => setEntities(entities)); - return () => subscription.unsubscribe(); - }, [query, updateOnValueChange]); - - return entities; -} \ No newline at end of file diff --git a/packages/react/src/useObservableValue.ts b/packages/react/src/useObservableValue.ts deleted file mode 100644 index 247d1c0e65..0000000000 --- a/packages/react/src/useObservableValue.ts +++ /dev/null @@ -1,19 +0,0 @@ -// HACK IMPORT FROM https://github.com/latticexyz/mud - ty Lattice! - -import { useEffect, useState } from "react"; -import { Observable } from "rxjs"; - -export function useObservableValue(observable: Observable, defaultValue: T): T; - -export function useObservableValue(observable: Observable): T | undefined; - -export function useObservableValue(observable: Observable, defaultValue?: T) { - const [value, setValue] = useState(defaultValue); - - useEffect(() => { - const subscription = observable.subscribe(setValue); - return () => subscription.unsubscribe(); - }, [observable]); - - return value; -} \ No newline at end of file diff --git a/packages/react/src/utils/useDeepMemo.ts b/packages/react/src/utils/useDeepMemo.ts deleted file mode 100644 index 4a72c9bdb4..0000000000 --- a/packages/react/src/utils/useDeepMemo.ts +++ /dev/null @@ -1,17 +0,0 @@ -// HACK IMPORT FROM https://github.com/latticexyz/mud - ty Lattice! - -import { useEffect, useState } from "react"; -import isEqual from "fast-deep-equal"; - -export const useDeepMemo = (currentValue: T): T => { - const [stableValue, setStableValue] = useState(currentValue); - - useEffect(() => { - if (!isEqual(currentValue, stableValue)) { - setStableValue(currentValue); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentValue]); - - return stableValue; -}; \ No newline at end of file diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json deleted file mode 100644 index d204a86e76..0000000000 --- a/packages/react/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react", - "moduleResolution": "bundler", - "target": "ES2020", - "module": "ESNext", - "declaration": true, - "outDir": "./dist", - "strict": true, - "esModuleInterop": true - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "node_modules", - "dist", - "**/*.test.ts" - ] -} \ No newline at end of file diff --git a/packages/react/yarn.lock b/packages/react/yarn.lock deleted file mode 100644 index 7bb1f31d0e..0000000000 --- a/packages/react/yarn.lock +++ /dev/null @@ -1,3426 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" - integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.22.0", "@babel/compat-data@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e" - integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.21.4": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.1.tgz#5de51c5206f4c6f5533562838337a603c1033cfd" - integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.22.0" - "@babel/helper-compilation-targets" "^7.22.1" - "@babel/helper-module-transforms" "^7.22.1" - "@babel/helpers" "^7.22.0" - "@babel/parser" "^7.22.0" - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.0" - -"@babel/generator@^7.22.0", "@babel/generator@^7.22.3", "@babel/generator@^7.7.2": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e" - integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A== - dependencies: - "@babel/types" "^7.22.3" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.3.tgz#c9b83d1ba74e163e023f008a3d3204588a7ceb60" - integrity sha512-ahEoxgqNoYXm0k22TvOke48i1PkavGu0qGCmcq9ugi6gnmvKNaMjKBSrZTnWUi1CFEeNAUiVba0Wtzm03aSkJg== - dependencies: - "@babel/types" "^7.22.3" - -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58" - integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ== - dependencies: - "@babel/compat-data" "^7.22.0" - "@babel/helper-validator-option" "^7.21.0" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz#ae3de70586cc757082ae3eba57240d42f468c41b" - integrity sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.22.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.22.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/helper-split-export-declaration" "^7.18.6" - semver "^6.3.0" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70" - integrity sha512-WWjdnfR3LPIe+0EY8td7WmjhytxXtjKAEpnAxun/hkNiyOaPlvGK+NZaBFIdi9ndYV3Gav7BpFvtUwnaJlwi1w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.3.1" - semver "^6.3.0" - -"@babel/helper-define-polyfill-provider@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8" - integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg== - dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" - integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== - -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" - integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== - dependencies: - "@babel/template" "^7.20.7" - "@babel/types" "^7.21.0" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-member-expression-to-functions@^7.22.0": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz#4b77a12c1b4b8e9e28736ed47d8b91f00976911f" - integrity sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA== - dependencies: - "@babel/types" "^7.22.3" - -"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" - integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== - dependencies: - "@babel/types" "^7.21.4" - -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5", "@babel/helper-module-transforms@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63" - integrity sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw== - dependencies: - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-module-imports" "^7.21.4" - "@babel/helper-simple-access" "^7.21.5" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.0" - -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" - integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== - -"@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" - -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz#38cf6e56f7dc614af63a21b45565dd623f0fdc95" - integrity sha512-ut4qrkE4AuSfrwHSps51ekR1ZY/ygrP1tp0WFm8oVq6nzc/hvfV/22JylndIbsf2U2M9LOMwiSddr6y+78j+OQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-member-expression-to-functions" "^7.22.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.0" - -"@babel/helper-simple-access@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" - integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== - dependencies: - "@babel/types" "^7.21.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" - integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== - dependencies: - "@babel/types" "^7.20.0" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-string-parser@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" - integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== - -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-option@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" - integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== - -"@babel/helper-wrap-function@^7.18.9": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" - integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== - dependencies: - "@babel/helper-function-name" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.5" - "@babel/types" "^7.20.5" - -"@babel/helpers@^7.22.0": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e" - integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w== - dependencies: - "@babel/template" "^7.21.9" - "@babel/traverse" "^7.22.1" - "@babel/types" "^7.22.3" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32" - integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz#a75be1365c0c3188c51399a662168c1c98108659" - integrity sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-transform-optional-chaining" "^7.22.3" - -"@babel/plugin-proposal-private-property-in-object@^7.21.0": - version "7.21.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" - integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.21.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-assertions@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" - integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-syntax-import-attributes@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.3.tgz#d7168f22b9b49a6cc1792cec78e06a18ad2e7b4b" - integrity sha512-i35jZJv6aO7hxEbIWQ41adVfOzjm9dcYDNeWlBMd8p0ZQRtNUCBrmGwZt+H5lb+oOC9a3svp956KP0oWGA1YsA== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" - integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz#2751948e9b7c6d771a8efa59340c15d4a2891ff8" - integrity sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" - integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-async-generator-functions@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.3.tgz#3ed99924c354fb9e80dabb2cc8d002c702e94527" - integrity sha512-36A4Aq48t66btydbZd5Fk0/xJqbpg/v4QWI4AH4cYHBXy9Mu42UOupZpebKFiCFNT9S9rJFcsld0gsv0ayLjtA== - dependencies: - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-transform-async-to-generator@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" - integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-remap-async-to-generator" "^7.18.9" - -"@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-block-scoping@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" - integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-class-properties@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.3.tgz#3407145e513830df77f0cef828b8b231c166fe4c" - integrity sha512-mASLsd6rhOrLZ5F3WbCxkzl67mmOnqik0zrg5W6D/X0QMW7HtvnoL1dRARLKIbMP3vXwkwziuLesPqWVGIl6Bw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-class-static-block@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.3.tgz#e352cf33567385c731a8f21192efeba760358773" - integrity sha512-5BirgNWNOx7cwbTJCOmKFJ1pZjwk5MUfMIwiBBvsirCJMZeQgs5pk6i1OlkVg+1Vef5LfBahFOrdCnAWvkVKMw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-transform-classes@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" - integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.20.7" - "@babel/helper-split-export-declaration" "^7.18.6" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" - integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/template" "^7.20.7" - -"@babel/plugin-transform-destructuring@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" - integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-dynamic-import@^7.22.1": - version "7.22.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.1.tgz#6c56afaf896a07026330cf39714532abed8d9ed1" - integrity sha512-rlhWtONnVBPdmt+jeewS0qSnMz/3yLFrqAP8hHC6EDcrYRSyuz9f9yQhHvVn2Ad6+yO9fHXac5piudeYrInxwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-export-namespace-from@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.3.tgz#9b8700aa495007d3bebac8358d1c562434b680b9" - integrity sha512-5Ti1cHLTDnt3vX61P9KZ5IG09bFXp4cDVFJIAeCZuxu9OXXJJZp5iP0n/rzM2+iAutJY+KWEyyHcRaHlpQ/P5g== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-transform-for-of@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" - integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== - dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-json-strings@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.3.tgz#a181b8679cf7c93e9d0e3baa5b1776d65be601a9" - integrity sha512-IuvOMdeOOY2X4hRNAT6kwbePtK21BUyrAEgLKviL8pL6AEEVUVcqtRdN/HJXBLGIbt9T3ETmXRnFedRRmQNTYw== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-logical-assignment-operators@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.3.tgz#9e021455810f33b0baccb82fb759b194f5dc36f0" - integrity sha512-CbayIfOw4av2v/HYZEsH+Klks3NC2/MFIR3QR8gnpGNNPEaq2fdlVCRYG/paKs7/5hvBLQ+H70pGWOHtlNEWNA== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-modules-amd@^7.20.11": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" - integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== - dependencies: - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-modules-commonjs@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" - integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ== - dependencies: - "@babel/helper-module-transforms" "^7.21.5" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-simple-access" "^7.21.5" - -"@babel/plugin-transform-modules-systemjs@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz#cc507e03e88d87b016feaeb5dae941e6ef50d91e" - integrity sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw== - dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-validator-identifier" "^7.19.1" - -"@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz#db6fb77e6b3b53ec3b8d370246f0b7cf67d35ab4" - integrity sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-new-target@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz#deb0377d741cbee2f45305868b9026dcd6dd96e2" - integrity sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.3.tgz#8c519f8bf5af94a9ca6f65cf422a9d3396e542b9" - integrity sha512-CpaoNp16nX7ROtLONNuCyenYdY/l7ZsR6aoVa7rW7nMWisoNoQNIH5Iay/4LDyRjKMuElMqXiBoOQCDLTMGZiw== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-transform-numeric-separator@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.3.tgz#02493070ca6685884b0eee705363ee4da2132ab0" - integrity sha512-+AF88fPDJrnseMh5vD9+SH6wq4ZMvpiTMHh58uLs+giMEyASFVhcT3NkoyO+NebFCNnpHJEq5AXO2txV4AGPDQ== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-transform-object-rest-spread@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.3.tgz#da6fba693effb8c203d8c3bdf7bf4e2567e802e9" - integrity sha512-38bzTsqMMCI46/TQnJwPPpy33EjLCc1Gsm2hRTF6zTMWnKsN61vdrpuzIEGQyKEhDSYDKyZHrrd5FMj4gcUHhw== - dependencies: - "@babel/compat-data" "^7.22.3" - "@babel/helper-compilation-targets" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.22.3" - -"@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" - -"@babel/plugin-transform-optional-catch-binding@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.3.tgz#e971a083fc7d209d9cd18253853af1db6d8dc42f" - integrity sha512-bnDFWXFzWY0BsOyqaoSXvMQ2F35zutQipugog/rqotL2S4ciFOKlRYUu9djt4iq09oh2/34hqfRR2k1dIvuu4g== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-transform-optional-chaining@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.3.tgz#5fd24a4a7843b76da6aeec23c7f551da5d365290" - integrity sha512-63v3/UFFxhPKT8j8u1jTTGVyITxl7/7AfOqK8C5gz1rHURPUGe3y5mvIf68eYKGoBNahtJnTxBKug4BQOnzeJg== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz#24477acfd2fd2bc901df906c9bf17fbcfeee900d" - integrity sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-private-methods@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.3.tgz#adac38020bab5047482d3297107c1f58e9c574f6" - integrity sha512-fC7jtjBPFqhqpPAE+O4LKwnLq7gGkD3ZmC2E3i4qWH34mH3gOg2Xrq5YMHUq6DM30xhqM1DNftiRaSqVjEG+ug== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-private-property-in-object@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56" - integrity sha512-C7MMl4qWLpgVCbXfj3UW8rR1xeCnisQ0cU7YJHV//8oNBS0aCIVg1vFnZXxOckHhEpQyqNNkWmvSEWnMLlc+Vw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-regenerator@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" - integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - regenerator-transform "^0.15.1" - -"@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-spread@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" - integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - -"@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-unicode-escapes@^7.21.5": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" - integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== - dependencies: - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-unicode-property-regex@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.3.tgz#597b6a614dc93eaae605ee293e674d79d32eb380" - integrity sha512-5ScJ+OmdX+O6HRuMGW4kv7RL9vIKdtdAj9wuWUKy1wbHY3jaM/UlyIiC1G7J6UJiiyMukjjK0QwL3P0vBd0yYg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-unicode-sets-regex@^7.22.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.3.tgz#7c14ee33fa69782b0101d0f7143d3fc73ce00700" - integrity sha512-hNufLdkF8vqywRp+P55j4FHXqAX2LRUccoZHH7AFn1pq5ZOO2ISKW9w13bFZVjBoTqeve2HOgoJCcaziJVhGNw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - -"@babel/preset-env@^7.21.4": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.4.tgz#c86a82630f0e8c61d9bb9327b7b896732028cbed" - integrity sha512-c3lHOjbwBv0TkhYCr+XCR6wKcSZ1QbQTVdSkZUaVpLv8CVWotBMArWUi5UAJrcrQaEnleVkkvaV8F/pmc/STZQ== - dependencies: - "@babel/compat-data" "^7.22.3" - "@babel/helper-compilation-targets" "^7.22.1" - "@babel/helper-plugin-utils" "^7.21.5" - "@babel/helper-validator-option" "^7.21.0" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.3" - "@babel/plugin-proposal-private-property-in-object" "^7.21.0" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.20.0" - "@babel/plugin-syntax-import-attributes" "^7.22.3" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.21.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.3" - "@babel/plugin-transform-async-to-generator" "^7.20.7" - "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.21.0" - "@babel/plugin-transform-class-properties" "^7.22.3" - "@babel/plugin-transform-class-static-block" "^7.22.3" - "@babel/plugin-transform-classes" "^7.21.0" - "@babel/plugin-transform-computed-properties" "^7.21.5" - "@babel/plugin-transform-destructuring" "^7.21.3" - "@babel/plugin-transform-dotall-regex" "^7.18.6" - "@babel/plugin-transform-duplicate-keys" "^7.18.9" - "@babel/plugin-transform-dynamic-import" "^7.22.1" - "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-export-namespace-from" "^7.22.3" - "@babel/plugin-transform-for-of" "^7.21.5" - "@babel/plugin-transform-function-name" "^7.18.9" - "@babel/plugin-transform-json-strings" "^7.22.3" - "@babel/plugin-transform-literals" "^7.18.9" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.3" - "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.20.11" - "@babel/plugin-transform-modules-commonjs" "^7.21.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.3" - "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.3" - "@babel/plugin-transform-new-target" "^7.22.3" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.3" - "@babel/plugin-transform-numeric-separator" "^7.22.3" - "@babel/plugin-transform-object-rest-spread" "^7.22.3" - "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-optional-catch-binding" "^7.22.3" - "@babel/plugin-transform-optional-chaining" "^7.22.3" - "@babel/plugin-transform-parameters" "^7.22.3" - "@babel/plugin-transform-private-methods" "^7.22.3" - "@babel/plugin-transform-private-property-in-object" "^7.22.3" - "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.21.5" - "@babel/plugin-transform-reserved-words" "^7.18.6" - "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.20.7" - "@babel/plugin-transform-sticky-regex" "^7.18.6" - "@babel/plugin-transform-template-literals" "^7.18.9" - "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.21.5" - "@babel/plugin-transform-unicode-property-regex" "^7.22.3" - "@babel/plugin-transform-unicode-regex" "^7.18.6" - "@babel/plugin-transform-unicode-sets-regex" "^7.22.3" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.22.4" - babel-plugin-polyfill-corejs2 "^0.4.3" - babel-plugin-polyfill-corejs3 "^0.8.1" - babel-plugin-polyfill-regenerator "^0.5.0" - core-js-compat "^3.30.2" - semver "^6.3.0" - -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/regjsgen@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" - integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== - -"@babel/runtime@^7.8.4": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" - integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== - dependencies: - regenerator-runtime "^0.13.11" - -"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9", "@babel/template@^7.3.3": - version "7.21.9" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb" - integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/parser" "^7.21.9" - "@babel/types" "^7.21.5" - -"@babel/traverse@^7.20.5", "@babel/traverse@^7.22.1", "@babel/traverse@^7.7.2": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0" - integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.22.3" - "@babel/helper-environment-visitor" "^7.22.1" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.22.4" - "@babel/types" "^7.22.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.22.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071" - integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA== - dependencies: - "@babel/helper-string-parser" "^7.21.5" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@cartridge/connector@^0.3.15": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@cartridge/connector/-/connector-0.3.18.tgz#4182260cfa195e145f00f8ebf0c6e090e7f7081d" - integrity sha512-PAfNTDiEF3cOJ+DgI1uUkxlbjlCzlepsY2o6kdwaansZfMNde6m6gdeucQLR+uN1jroIpy1949nfxOmFIl9ldw== - dependencies: - "@cartridge/controller" "0.3.18" - "@starknet-react/core" "^0.16.0" - starknet "^4.22.0" - -"@cartridge/controller@0.3.18": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@cartridge/controller/-/controller-0.3.18.tgz#4cd309a7c88b7a3aee2e4029dc5cb8269269a4dc" - integrity sha512-rsBTk0VUi9CKz80dSmUd5lb3I7hUKR0SepUdNdkbHvMdw/HnsobB4YY9kNEDzkv0ttljxFyTZjZGsM99N+kkYg== - dependencies: - "@cartridge/penpal" "^6.2.3" - base64url "^3.0.1" - bn.js "^5.2.1" - cbor-x "^1.5.0" - fast-deep-equal "^3.1.3" - query-string "^7.1.1" - starknet "^4.17.1" - -"@cartridge/penpal@^6.2.3": - version "6.2.3" - resolved "https://registry.yarnpkg.com/@cartridge/penpal/-/penpal-6.2.3.tgz#c94e3ca6a48732e3192e07bb810cb11d426a71b0" - integrity sha512-K8h9VqBfFPXcAFQNnvgBnejF/dp7249pS4jXu3NhNYR6JqMQxtcrDqfnPmJvbF4ECEBs+8Z2UiwlRQiKt5nNsg== - -"@cbor-extract/cbor-extract-darwin-arm64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.1.1.tgz#5721f6dd3feae0b96d23122853ce977e0671b7a6" - integrity sha512-blVBy5MXz6m36Vx0DfLd7PChOQKEs8lK2bD1WJn/vVgG4FXZiZmZb2GECHFvVPA5T7OnODd9xZiL3nMCv6QUhA== - -"@cbor-extract/cbor-extract-darwin-x64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.1.1.tgz#c25e7d0133950d87d101d7b3afafea8d50d83f5f" - integrity sha512-h6KFOzqk8jXTvkOftyRIWGrd7sKQzQv2jVdTL9nKSf3D2drCvQB/LHUxAOpPXo3pv2clDtKs3xnHalpEh3rDsw== - -"@cbor-extract/cbor-extract-linux-arm64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.1.1.tgz#48f78e7d8f0fcc84ed074b6bfa6d15dd83187c63" - integrity sha512-SxAaRcYf8S0QHaMc7gvRSiTSr7nUYMqbUdErBEu+HYA4Q6UNydx1VwFE68hGcp1qvxcy9yT5U7gA+a5XikfwSQ== - -"@cbor-extract/cbor-extract-linux-arm@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.1.1.tgz#7507d346389cb682e44fab8fae9534edd52e2e41" - integrity sha512-ds0uikdcIGUjPyraV4oJqyVE5gl/qYBpa/Wnh6l6xLE2lj/hwnjT2XcZCChdXwW/YFZ1LUHs6waoYN8PmK0nKQ== - -"@cbor-extract/cbor-extract-linux-x64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.1.1.tgz#b7c1d2be61c58ec18d58afbad52411ded63cd4cd" - integrity sha512-GVK+8fNIE9lJQHAlhOROYiI0Yd4bAZ4u++C2ZjlkS3YmO6hi+FUxe6Dqm+OKWTcMpL/l71N6CQAmaRcb4zyJuA== - -"@cbor-extract/cbor-extract-win32-x64@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.1.1.tgz#21b11a1a3f18c3e7d62fd5f87438b7ed2c64c1f7" - integrity sha512-2Niq1C41dCRIDeD8LddiH+mxGlO7HJ612Ll3D/E73ZWBmycued+8ghTr/Ho3CMOWPUEr08XtyBMVXAjqF+TcKw== - -"@ethersproject/bytes@^5.6.1": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" - integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== - dependencies: - "@ethersproject/logger" "^5.7.0" - -"@ethersproject/logger@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" - integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" - integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== - dependencies: - "@jest/types" "^29.5.0" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - slash "^3.0.0" - -"@jest/core@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" - integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== - dependencies: - "@jest/console" "^29.5.0" - "@jest/reporters" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.5.0" - jest-config "^29.5.0" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-resolve-dependencies "^29.5.0" - jest-runner "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - jest-watcher "^29.5.0" - micromatch "^4.0.4" - pretty-format "^29.5.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" - integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== - dependencies: - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - jest-mock "^29.5.0" - -"@jest/expect-utils@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" - integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== - dependencies: - jest-get-type "^29.4.3" - -"@jest/expect@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" - integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== - dependencies: - expect "^29.5.0" - jest-snapshot "^29.5.0" - -"@jest/fake-timers@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" - integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== - dependencies: - "@jest/types" "^29.5.0" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-util "^29.5.0" - -"@jest/globals@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" - integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/types" "^29.5.0" - jest-mock "^29.5.0" - -"@jest/reporters@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" - integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - jest-worker "^29.5.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" - integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== - dependencies: - "@sinclair/typebox" "^0.25.16" - -"@jest/source-map@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" - integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== - dependencies: - "@jridgewell/trace-mapping" "^0.3.15" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" - integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== - dependencies: - "@jest/console" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" - integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== - dependencies: - "@jest/test-result" "^29.5.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - slash "^3.0.0" - -"@jest/transform@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" - integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@^29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" - integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== - dependencies: - "@jest/schemas" "^29.4.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@latticexyz/recs@^1.43.0": - version "1.43.0" - resolved "https://registry.yarnpkg.com/@latticexyz/recs/-/recs-1.43.0.tgz#51d8bb0d164ef3c79b86bb8201870177d2eab91b" - integrity sha512-rWCtFeqeoPYT+hsu1whd47cVSvRtOrBe1tGngqxZdiYnLx1bbj5rW9xXzS1Vg+8PgLeX034yZCoV+RjcaGIRPg== - -"@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" - integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== - -"@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" - integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== - -"@scure/base@~1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" - integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== - -"@scure/bip32@1.1.5": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" - integrity sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw== - dependencies: - "@noble/hashes" "~1.2.0" - "@noble/secp256k1" "~1.7.0" - "@scure/base" "~1.1.0" - -"@scure/bip39@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" - integrity sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg== - dependencies: - "@noble/hashes" "~1.2.0" - "@scure/base" "~1.1.0" - -"@sinclair/typebox@^0.25.16": - version "0.25.24" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" - integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== - -"@sinonjs/commons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" - integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.2.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz#b3e322a34c5f26e3184e7f6115695f299c1b1194" - integrity sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@starknet-react/core@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@starknet-react/core/-/core-0.16.0.tgz#c22eec381e39b2635820056f99c701f3349cc686" - integrity sha512-VWBAilTrF3RAuD2laQJepx3Zhd2I8CHCA8p81hzAKlPyiUXIkLYLlGncckbRXX+a/wBSGZs+FNoqsUE0nOUYCg== - dependencies: - "@tanstack/react-query" "^4.3.4" - get-starknet-core "^2.0.0" - immutable "^4.0.0" - -"@tanstack/query-core@4.29.11": - version "4.29.11" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.29.11.tgz#fa338f7d6897c6be5de6d8dabd603d9b78ee48c7" - integrity sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ== - -"@tanstack/react-query@^4.3.4": - version "4.29.12" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.29.12.tgz#de111cf1d6c389b86acacfaf972302914cfa1208" - integrity sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA== - dependencies: - "@tanstack/query-core" "4.29.11" - use-sync-external-store "^1.2.0" - -"@types/babel__core@^7.1.14": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" - integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.4" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" - integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" - integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== - dependencies: - "@babel/types" "^7.20.7" - -"@types/bn.js@*", "@types/bn.js@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" - integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== - dependencies: - "@types/node" "*" - -"@types/elliptic@^6.4.14": - version "6.4.14" - resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.14.tgz#7bbaad60567a588c1f08b10893453e6b9b4de48e" - integrity sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ== - dependencies: - "@types/bn.js" "*" - -"@types/events@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== - -"@types/graceful-fs@^4.1.3": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" - integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" - integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^29.5.0": - version "29.5.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b" - integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - -"@types/node@*": - version "20.2.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb" - integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== - -"@types/node@^18.15.11": - version "18.16.16" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.16.tgz#3b64862856c7874ccf7439e6bab872d245c86d8e" - integrity sha512-NpaM49IGQQAUlBhHMF82QH80J08os4ZmyF9MkpCzWAGuOHqE4gTEbhzd7L3l5LmWuZ6E0OiC1FweQ4tsiW35+g== - -"@types/prettier@^2.1.5": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" - integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== - -"@types/prop-types@*": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== - -"@types/react-dom@^18.0.11": - version "18.2.4" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.4.tgz#13f25bfbf4e404d26f62ac6e406591451acba9e0" - integrity sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^18.0.33": - version "18.2.8" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.8.tgz#a77dcffe4e9af148ca4aa8000c51a1e8ed99e2c8" - integrity sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/scheduler@*": - version "0.16.3" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/yargs-parser@*": - version "21.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" - integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== - -"@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -anymatch@^3.0.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -babel-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" - integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== - dependencies: - "@jest/transform" "^29.5.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.5.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" - integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-plugin-polyfill-corejs2@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd" - integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw== - dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.4.0" - semver "^6.1.1" - -babel-plugin-polyfill-corejs3@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a" - integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.0" - core-js-compat "^3.30.1" - -babel-plugin-polyfill-regenerator@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380" - integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.0" - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" - integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== - dependencies: - babel-plugin-jest-hoist "^29.5.0" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64url@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" - integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== - -bignumber.js@^9.0.0: - version "9.1.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" - integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== - -bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== - -bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -browserslist@^4.21.3, browserslist@^4.21.5: - version "4.21.7" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551" - integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA== - dependencies: - caniuse-lite "^1.0.30001489" - electron-to-chromium "^1.4.411" - node-releases "^2.0.12" - update-browserslist-db "^1.0.11" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001489: - version "1.0.30001495" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz#64a0ccef1911a9dcff647115b4430f8eff1ef2d9" - integrity sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg== - -cbor-extract@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.1.1.tgz#f154b31529fdb6b7c70fb3ca448f44eda96a1b42" - integrity sha512-1UX977+L+zOJHsp0mWFG13GLwO6ucKgSmSW6JTl8B9GUvACvHeIVpFqhU92299Z6PfD09aTXDell5p+lp1rUFA== - dependencies: - node-gyp-build-optional-packages "5.0.3" - optionalDependencies: - "@cbor-extract/cbor-extract-darwin-arm64" "2.1.1" - "@cbor-extract/cbor-extract-darwin-x64" "2.1.1" - "@cbor-extract/cbor-extract-linux-arm" "2.1.1" - "@cbor-extract/cbor-extract-linux-arm64" "2.1.1" - "@cbor-extract/cbor-extract-linux-x64" "2.1.1" - "@cbor-extract/cbor-extract-win32-x64" "2.1.1" - -cbor-x@^1.5.0: - version "1.5.3" - resolved "https://registry.yarnpkg.com/cbor-x/-/cbor-x-1.5.3.tgz#f8252fec7cab86b66c500e0c991788618e6638de" - integrity sha512-adrN0S67C7jY2hgqeGcw+Uj6iEGLQa5D/p6/9YNl5AaVIYJaJz/bARfWsP8UikBZWbhS27LN0DJK4531vo9ODw== - optionalDependencies: - cbor-extract "^2.1.1" - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -ci-info@^3.2.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== - -cjs-module-lexer@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" - integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -core-js-compat@^3.30.1, core-js-compat@^3.30.2: - version "3.30.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b" - integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA== - dependencies: - browserslist "^4.21.5" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -csstype@^3.0.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== - -debug@^4.1.0, debug@^4.1.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decode-uri-component@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== - -electron-to-chromium@^1.4.411: - version "1.4.423" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.423.tgz#99567f3a0563fe0d1d0931e9ce851bca239f6658" - integrity sha512-y4A7YfQcDGPAeSWM1IuoWzXpg9RY1nwHzHSwRtCSQFp9FgAVDgdWlFf0RbdWfLWQ2WUI+bddUgk5RgTjqRE6FQ== - -elliptic@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -ethereum-cryptography@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz#5ccfa183e85fdaf9f9b299a79430c044268c9b3a" - integrity sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw== - dependencies: - "@noble/hashes" "1.2.0" - "@noble/secp256k1" "1.7.1" - "@scure/bip32" "1.1.5" - "@scure/bip39" "1.1.1" - -events@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@^29.0.0, expect@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" - integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== - dependencies: - "@jest/expect-utils" "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - -fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fb-watchman@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-starknet-core@^2.0.0, get-starknet-core@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/get-starknet-core/-/get-starknet-core-2.1.0.tgz#f8dee81c0b050fbb6d284fad81a5bbdad79ef74c" - integrity sha512-sT9fxIiM3maEqz1cVggiB/jGn+nUCavhhhJRnU/G1zKMmJFYORjRNohEZ1xXgRt6MFVUNPnAzxQrhI1F645VNA== - -get-starknet@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/get-starknet/-/get-starknet-2.1.0.tgz#990e275dd1ce3b531b9b8a8a51f9699beba8951b" - integrity sha512-cJX1M/IsFYBpMI/oyn05CPiM3a/95RLesYkc4pCeuO6x3upSaJDa6kanWi294qPRBnhwanAIB3zeGhVZ9ymBMg== - dependencies: - bowser "^2.11.0" - get-starknet-core "^2.1.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -immutable@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be" - integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== - -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-core-module@^2.11.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== - dependencies: - has "^1.0.3" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isomorphic-fetch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" - integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== - dependencies: - node-fetch "^2.6.1" - whatwg-fetch "^3.4.1" - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" - integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== - -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.1.3: - version "3.1.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" - integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" - integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== - dependencies: - execa "^5.0.0" - p-limit "^3.1.0" - -jest-circus@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" - integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^0.7.0" - is-generator-fn "^2.0.0" - jest-each "^29.5.0" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - p-limit "^3.1.0" - pretty-format "^29.5.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" - integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== - dependencies: - "@jest/core" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - import-local "^3.0.2" - jest-config "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - prompts "^2.0.1" - yargs "^17.3.1" - -jest-config@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" - integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.5.0" - "@jest/types" "^29.5.0" - babel-jest "^29.5.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.5.0" - jest-environment-node "^29.5.0" - jest-get-type "^29.4.3" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-runner "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.5.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" - integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" - -jest-docblock@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" - integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" - integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== - dependencies: - "@jest/types" "^29.5.0" - chalk "^4.0.0" - jest-get-type "^29.4.3" - jest-util "^29.5.0" - pretty-format "^29.5.0" - -jest-environment-node@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" - integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" - -jest-get-type@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" - integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== - -jest-haste-map@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" - integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== - dependencies: - "@jest/types" "^29.5.0" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" - jest-worker "^29.5.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" - integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== - dependencies: - jest-get-type "^29.4.3" - pretty-format "^29.5.0" - -jest-matcher-utils@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" - integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== - dependencies: - chalk "^4.0.0" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" - -jest-message-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" - integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.5.0" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.5.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== - dependencies: - "@jest/types" "^29.5.0" - "@types/node" "*" - jest-util "^29.5.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" - integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== - -jest-resolve-dependencies@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" - integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== - dependencies: - jest-regex-util "^29.4.3" - jest-snapshot "^29.5.0" - -jest-resolve@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" - integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.5.0" - jest-validate "^29.5.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" - integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== - dependencies: - "@jest/console" "^29.5.0" - "@jest/environment" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.4.3" - jest-environment-node "^29.5.0" - jest-haste-map "^29.5.0" - jest-leak-detector "^29.5.0" - jest-message-util "^29.5.0" - jest-resolve "^29.5.0" - jest-runtime "^29.5.0" - jest-util "^29.5.0" - jest-watcher "^29.5.0" - jest-worker "^29.5.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" - integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/globals" "^29.5.0" - "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" - integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.5.0" - graceful-fs "^4.2.9" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - natural-compare "^1.4.0" - pretty-format "^29.5.0" - semver "^7.3.5" - -jest-util@^29.0.0, jest-util@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" - integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== - dependencies: - "@jest/types" "^29.5.0" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" - integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== - dependencies: - "@jest/types" "^29.5.0" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.4.3" - leven "^3.1.0" - pretty-format "^29.5.0" - -jest-watcher@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" - integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== - dependencies: - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.5.0" - string-length "^4.0.1" - -jest-worker@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" - integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== - dependencies: - "@types/node" "*" - jest-util "^29.5.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" - integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== - dependencies: - "@jest/core" "^29.5.0" - "@jest/types" "^29.5.0" - import-local "^3.0.2" - jest-cli "^29.5.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" - integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== - dependencies: - bignumber.js "^9.0.0" - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@1.x: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -mobx@^6.9.0: - version "6.9.0" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.9.0.tgz#8a894c26417c05bed2cf7499322e589ee9787397" - integrity sha512-HdKewQEREEJgsWnErClfbFoVebze6rGazxFLU/XUyrII8dORfVszN1V0BMRnQSzcgsNNtkX8DHj3nC6cdWE9YQ== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -node-fetch@^2.6.1: - version "2.6.11" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" - integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== - dependencies: - whatwg-url "^5.0.0" - -node-gyp-build-optional-packages@5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" - integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-releases@^2.0.12: - version "2.0.12" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" - integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pretty-format@^29.0.0, pretty-format@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" - integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== - dependencies: - "@jest/schemas" "^29.4.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -pure-rand@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" - integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== - -query-string@^7.1.1: - version "7.1.3" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" - integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== - dependencies: - decode-uri-component "^0.2.2" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -react-is@^18.0.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - -regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -regenerator-transform@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexpu-core@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" - integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== - dependencies: - "@babel/regjsgen" "^0.8.0" - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== - -resolve@^1.14.2, resolve@^1.20.0: - version "1.22.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" - integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== - dependencies: - is-core-module "^2.11.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -rxjs@^7.8.1: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - -semver@7.x, semver@^7.3.5: - version "7.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" - integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== - dependencies: - lru-cache "^6.0.0" - -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -stack-utils@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -starknet@^4.17.1, starknet@^4.22.0: - version "4.22.0" - resolved "https://registry.yarnpkg.com/starknet/-/starknet-4.22.0.tgz#8d0c628e2a8e868ee9b4757afe89f07b05ec55ff" - integrity sha512-jC9Taxb6a/ht9zmS1LU/DSLfwJKpgCJnE9AktVksc5SE/+jQMpqxsq6fm7PRiqupjiqRC1DOS8N47cj+KaGv4Q== - dependencies: - "@ethersproject/bytes" "^5.6.1" - bn.js "^5.2.1" - elliptic "^6.5.4" - ethereum-cryptography "^1.0.3" - hash.js "^1.1.7" - isomorphic-fetch "^3.0.0" - json-bigint "^1.0.0" - minimalistic-assert "^1.0.1" - pako "^2.0.4" - ts-custom-error "^3.3.1" - url-join "^4.0.1" - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -ts-custom-error@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-3.3.1.tgz#8bd3c8fc6b8dc8e1cb329267c45200f1e17a65d1" - integrity sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A== - -ts-jest@^29.1.0: - version "29.1.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.0.tgz#4a9db4104a49b76d2b368ea775b6c9535c603891" - integrity sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA== - dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^29.0.0" - json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "7.x" - yargs-parser "^21.0.1" - -tslib@^2.1.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" - integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^3.11.1: - version "3.11.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.11.1.tgz#d8e62c7f42e14537d5b8796de5450d541f3a33a7" - integrity sha512-aCuRNRERRVh33lgQaJRlUxZqzfhzwTrsE98Mc3o3VXqmiaQdHacgUtJ0esp+7MvZ92qhtzKPeusaX6vIEcoreA== - -typescript@^5.0.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" - integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -url-join@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - -use-sync-external-store@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== - -v8-to-istanbul@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" - integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-fetch@^3.4.1: - version "3.6.2" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" - integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@^21.0.1, yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/packages/readme.md b/packages/readme.md deleted file mode 100644 index 6eae005f0b..0000000000 --- a/packages/readme.md +++ /dev/null @@ -1,37 +0,0 @@ -# Dojo NPM - - -#### Packages -- [Dojo Core](./core) -- [Dojo React](./react/) - -#### Examples -- [Create React App](./examples/create-react-app) - -## Dojo Core - -Dojo core aims to be a set of low level reusable functions that integrate seamlessly into a Dojo world. Design goals: - -- Simple and non-framework orientated, the core should represent the lowest level of the Dojo js stack - - -## Dojo React - -Dojo React aims to expose a set of React hooks using Dojo Core for seamless integration into a Dojo world for any React based apps. - - -## Examples - -### Create React App - -The bare minimum Dojo world using the Dojo starter positions - - -## Contributing - -You will need to sym link the local packages for them to work correctly - -``` -cd react && yarn link -cd core && yarn link -``` diff --git a/scripts/cairo_test.sh b/scripts/cairo_test.sh index ea08ab6c25..393bb96c61 100755 --- a/scripts/cairo_test.sh +++ b/scripts/cairo_test.sh @@ -1,8 +1,8 @@ #!/bin/bash set -euxo pipefail -cargo run --bin sozo -- --manifest-path crates/dojo-core/Scarb.toml test +cargo run -r --bin sozo -- --manifest-path crates/dojo-core/Scarb.toml test # cargo run --bin sozo -- test crates/dojo-physics -cargo run --bin sozo -- --manifest-path crates/dojo-erc/Scarb.toml test +cargo run -r --bin sozo -- --manifest-path crates/dojo-erc/Scarb.toml test # cargo run --bin sozo -- test crates/dojo-defi -cargo run --bin sozo -- --manifest-path examples/ecs/Scarb.toml test +cargo run -r --bin sozo -- --manifest-path examples/ecs/Scarb.toml test