├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── feature.yml ├── PULL_REQUEST_TEMPLATE.md ├── assets │ ├── s1.png │ ├── s2.png │ ├── s3.png │ └── s4.png ├── dependabot.yml ├── pr-title-checker-config.json └── workflows │ ├── pr-title-checker.yml │ └── test-rs.yml ├── .gitignore ├── CODEOWNERS ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bin └── shadow-reth │ ├── Cargo.toml │ └── src │ └── main.rs ├── book.toml ├── crates ├── common │ ├── Cargo.toml │ └── src │ │ ├── db.rs │ │ ├── hex.rs │ │ ├── lib.rs │ │ └── types.rs ├── exex │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── contracts.rs │ │ ├── db.rs │ │ ├── execution.rs │ │ └── lib.rs └── rpc │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── apis │ ├── get_logs.rs │ ├── mod.rs │ ├── subscribe.rs │ └── types.rs │ ├── lib.rs │ └── shadow_logs_query.rs ├── rustfmt.toml └── shadow.json.example /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a bug report 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for taking the time to fill out this bug report! Please provide as much detail as possible. 8 | 9 | If you believe you have found a vulnerability, please provide details [here](mailto:jon@shadow.xyz) instead. 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: Describe the bug 14 | description: | 15 | A clear and concise description of what the bug is. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: reproduction-steps 20 | attributes: 21 | label: Steps to reproduce 22 | description: Please provide any steps you think might be relevant to reproduce the bug. 23 | placeholder: | 24 | Steps to reproduce: 25 | 26 | 1. Start '...' 27 | 2. Then '...' 28 | 3. Check '...' 29 | 4. See error 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: logs 34 | attributes: 35 | label: Node logs 36 | description: | 37 | If applicable, please provide the node logs leading up to the bug. 38 | 39 | **Please also provide debug logs.** By default, these can be found in: 40 | 41 | - `~/.cache/reth/logs` on Linux 42 | - `~/Library/Caches/reth/logs` on macOS 43 | - `%localAppData%/reth/logs` on Windows 44 | render: text 45 | validations: 46 | required: false 47 | - type: dropdown 48 | id: platform 49 | attributes: 50 | label: Platform(s) 51 | description: What platform(s) did this occur on? 52 | multiple: true 53 | options: 54 | - Linux (x86) 55 | - Linux (ARM) 56 | - Mac (Intel) 57 | - Mac (Apple Silicon) 58 | - Windows (x86) 59 | - Windows (ARM) 60 | validations: 61 | required: true 62 | - type: textarea 63 | id: client-version 64 | attributes: 65 | label: What version/commit are you on? 66 | description: This can be obtained with `shadow-reth --version` 67 | validations: 68 | required: true 69 | - type: checkboxes 70 | id: terms 71 | attributes: 72 | label: Code of Conduct 73 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/shadow-hq/shadow-reth/blob/main/CONTRIBUTING.md#code-of-conduct) 74 | options: 75 | - label: I agree to follow the Code of Conduct 76 | required: true 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a feature 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Please ensure that the feature has not already been requested in the issue tracker. 8 | - type: textarea 9 | attributes: 10 | label: Describe the feature 11 | description: | 12 | Please describe the feature and what it is aiming to solve, if relevant. 13 | 14 | If the feature is for a crate, please include a proposed API surface. 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Additional context 20 | description: Add any other context to the feature (like screenshots, resources) 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## Motivation 9 | 10 | 15 | 16 | ## Solution 17 | 18 | 22 | -------------------------------------------------------------------------------- /.github/assets/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadow-hq/shadow-reth/22cbb74e817f3c8a00caf8b3c20e60b18fcf18d9/.github/assets/s1.png -------------------------------------------------------------------------------- /.github/assets/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadow-hq/shadow-reth/22cbb74e817f3c8a00caf8b3c20e60b18fcf18d9/.github/assets/s2.png -------------------------------------------------------------------------------- /.github/assets/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadow-hq/shadow-reth/22cbb74e817f3c8a00caf8b3c20e60b18fcf18d9/.github/assets/s3.png -------------------------------------------------------------------------------- /.github/assets/s4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadow-hq/shadow-reth/22cbb74e817f3c8a00caf8b3c20e60b18fcf18d9/.github/assets/s4.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/pr-title-checker-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LABEL": { 3 | "name": "" 4 | }, 5 | "CHECKS": { 6 | "regexp": "(?:fix|chore|feat|deploy|meta)(?:\\(.*\\))?:\\s[A-Z0-9]+", 7 | "regexpFlags": "i" 8 | }, 9 | "MESSAGES": { 10 | "success": "All OK", 11 | "failure": "Failing CI test", 12 | "notice": "" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/pr-title-checker.yml: -------------------------------------------------------------------------------- 1 | name: "PR Title Checker" 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: thehanimo/pr-title-checker@v1.4.1 10 | with: 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | pass_on_octokit_error: false 13 | configuration_path: .github/pr-title-checker-config.json #(optional. defaults to .github/pr-title-checker-config.json) 14 | -------------------------------------------------------------------------------- /.github/workflows/test-rs.yml: -------------------------------------------------------------------------------- 1 | name: test-rs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 12 | pull-requests: read 13 | 14 | # Max 1 concurrent workflow run per PR. 15 | # 16 | # Use `github.head_ref` instead of `github.ref` so as not to limit 17 | # concurrency on non-PR pushes. "`github.head_ref` is only defined on 18 | # `pull_request` events" [1] 19 | # 20 | # References: 21 | # [1] https://docs.github.com/en/actions/using-jobs/using-concurrency 22 | # [2] https://docs.github.com/en/actions/learn-github-actions/contexts#github-context 23 | # [3] https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request 24 | concurrency: 25 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | check: 30 | name: Check 31 | # Note: We specify Ubuntu 22.04 here because we were getting errors 32 | # similar to https://github.com/actions/runner-images/issues/6709 33 | runs-on: ubuntu-22.04 34 | steps: 35 | - name: Checkout sources 36 | uses: actions/checkout@v2 37 | 38 | - name: Set GitHub Personal Access Token 39 | run: git config --global url."https://${{ secrets.GH_PAT }}:@github.com/".insteadOf "https://github.com/" 40 | 41 | - name: Install stable toolchain 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: 1.76.0 46 | override: true 47 | 48 | - uses: Swatinem/rust-cache@v2 49 | with: 50 | cache-on-failure: true 51 | 52 | - name: Run cargo check 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: check 56 | 57 | check-all: 58 | name: Check (all features) 59 | # Note: We specify Ubuntu 22.04 here because we were getting errors 60 | # similar to https://github.com/actions/runner-images/issues/6709 61 | runs-on: ubuntu-22.04 62 | steps: 63 | - name: Checkout sources 64 | uses: actions/checkout@v2 65 | 66 | - name: Set GitHub Personal Access Token 67 | run: git config --global url."https://${{ secrets.GH_PAT }}:@github.com/".insteadOf "https://github.com/" 68 | 69 | - name: Install stable toolchain 70 | uses: actions-rs/toolchain@v1 71 | with: 72 | profile: minimal 73 | toolchain: 1.76.0 74 | override: true 75 | 76 | - uses: Swatinem/rust-cache@v2 77 | with: 78 | cache-on-failure: true 79 | 80 | - name: Run cargo check 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: check 84 | args: --all-features 85 | 86 | test: 87 | name: Test Suite 88 | runs-on: ubuntu-22.04 89 | 90 | steps: 91 | - name: Checkout sources 92 | uses: actions/checkout@v2 93 | 94 | - name: Set GitHub Personal Access Token 95 | run: git config --global url."https://${{ secrets.GH_PAT }}:@github.com/".insteadOf "https://github.com/" 96 | # Needed to compile properly on Ubuntu 97 | # https://github.com/rust-lang/rust/issues/25289 98 | - name: Install gcc-multilib 99 | run: sudo apt update && sudo apt install gcc-multilib llvm-dev pkg-config libclang-dev 100 | 101 | # IMPORTANT: Be careful with updating the toolchain version here. We have run into issues 102 | # with even minor upgrades to the latest stable toolchain, so we're pinning 1.76.0 for now. 103 | # 104 | # It's also important to make sure that this version is the same as the one used in our 105 | # Dockerfiles when building the service binaries. 106 | - name: Install stable toolchain 107 | uses: actions-rs/toolchain@v1 108 | with: 109 | profile: minimal 110 | toolchain: 1.76.0 111 | override: true 112 | 113 | - uses: Swatinem/rust-cache@v2 114 | with: 115 | cache-on-failure: true 116 | 117 | - name: "Set up Cloud for local infra" 118 | uses: "google-github-actions/setup-gcloud@v1" 119 | 120 | - name: Run sccache-cache 121 | uses: mozilla-actions/sccache-action@v0.0.5 122 | with: 123 | version: "v0.5.4" 124 | 125 | - name: Run cargo test 126 | uses: actions-rs/cargo@v1 127 | with: 128 | command: test 129 | args: --all-features 130 | env: 131 | # sccache 132 | SCCACHE_GHA_ENABLED: "true" 133 | RUSTC_WRAPPER: "sccache" 134 | CARGO_INCREMENTAL: 0 135 | 136 | fmt: 137 | name: Cargo Format 138 | # Note: We specify Ubuntu 22.04 here because we were getting errors 139 | # similar to https://github.com/actions/runner-images/issues/6709 140 | runs-on: ubuntu-22.04 141 | steps: 142 | - name: Checkout sources 143 | uses: actions/checkout@v2 144 | 145 | - name: Set GitHub Personal Access Token 146 | run: git config --global url."https://${{ secrets.GH_PAT }}:@github.com/".insteadOf "https://github.com/" 147 | 148 | - name: Install nightly toolchain 149 | uses: actions-rs/toolchain@v1 150 | with: 151 | profile: minimal 152 | toolchain: nightly 153 | override: true 154 | components: rustfmt 155 | 156 | - uses: Swatinem/rust-cache@v2 157 | with: 158 | cache-on-failure: true 159 | 160 | - name: Run cargo fmt 161 | uses: actions-rs/cargo@v1 162 | with: 163 | command: fmt 164 | args: --all -- --check 165 | 166 | clippy: 167 | name: Cargo Clippy 168 | # Note: We specify Ubuntu 22.04 here because we were getting errors 169 | # similar to https://github.com/actions/runner-images/issues/6709 170 | runs-on: ubuntu-22.04 171 | steps: 172 | - name: Checkout sources 173 | uses: actions/checkout@v2 174 | 175 | - name: Set GitHub Personal Access Token 176 | run: git config --global url."https://${{ secrets.GH_PAT }}:@github.com/".insteadOf "https://github.com/" 177 | 178 | - uses: Swatinem/rust-cache@v2 179 | with: 180 | cache-on-failure: true 181 | 182 | - name: Install stable toolchain 183 | uses: actions-rs/toolchain@v1 184 | with: 185 | profile: minimal 186 | toolchain: 1.76.0 187 | override: true 188 | components: clippy 189 | 190 | - name: Run cargo clippy 191 | uses: actions-rs/cargo@v1 192 | with: 193 | command: clippy 194 | args: --all-features -- -D warnings 195 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | 10 | # Unignore Makefile 11 | !Makefile 12 | 13 | # Unignore Dockerfile 14 | !Dockerfile 15 | 16 | # Unignore LICENSE* files 17 | !LICENSE* 18 | 19 | # Unignore CODEOWNERS file 20 | !CODEOWNERS 21 | 22 | # Ignore .env 23 | .env 24 | 25 | # Ignore generated Protobuf files 26 | *.pb.go 27 | *.pb.gw.go 28 | 29 | # Ignore local .terraform directories 30 | **/.terraform/* 31 | 32 | # Ignore .tfstate files 33 | *.tfstate 34 | *.tfstate.* 35 | 36 | # Ignore terraform crash log files 37 | crash.log 38 | crash.*.log 39 | 40 | # Ignore .idea/ files 41 | .idea 42 | 43 | # Ignore .vscode/ files 44 | .vscode 45 | 46 | # Ignore .shadow/ files 47 | .shadow 48 | 49 | # Ignore .rust-compiler.toml 50 | .rust-analyzer.toml 51 | 52 | # Ignore Rust target files 53 | /target 54 | 55 | # Ignore DS_Store files 56 | .DS_Store 57 | 58 | # Ignore out/ files 59 | out 60 | 61 | # Ignore flamegraph.svg files 62 | flamegraph.svg 63 | 64 | # Ignore vscode workspaces 65 | *.code-workspace 66 | 67 | # Ignore node modules 68 | node_modules 69 | 70 | # Ignore dump.rdb files 71 | dump.rdb 72 | 73 | # Ignore shadow.json 74 | shadow.json 75 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jon-becker @emilyhsia 2 | crates/exex @jon-becker 3 | crates/rpc @deekerno 4 | .github/ @emhsia 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## Contributing to Shadow Reth 6 | 7 | Thanks for your interest in improving Shadow Reth! 8 | 9 | There are multiple opportunities to contribute at any level. It doesn't matter if you are just getting started with Rust 10 | or are the most weathered expert, we can use your help. 11 | 12 | **No contribution is too small and all contributions are valued.** 13 | 14 | This document will help you get started. **Do not let the document intimidate you**. 15 | It should be considered as a guide to help you navigate the process. 16 | 17 | If you contribute to this project, your contributions will be made to the project under both Apache 2.0 and the MIT 18 | license. 19 | 20 | ### Code of Conduct 21 | 22 | The Reth project adheres to the [Rust Code of Conduct][rust-coc]. This code of conduct describes the _minimum_ behavior 23 | expected from all contributors. 24 | 25 | Instances of violations of the Code of Conduct can be reported by contacting the team 26 | at [gm@shadow.xyz](mailto:gm@shadow.xyz). 27 | 28 | ### Ways to contribute 29 | 30 | There are fundamentally three ways an individual can contribute: 31 | 32 | 1. **By opening an issue:** For example, if you believe that you have uncovered a bug 33 | in Shadow Reth, creating a new issue in the issue tracker is the way to report it. 34 | 2. **By adding context:** Providing additional context to existing issues, 35 | such as screenshots and code snippets to help resolve issues. 36 | 3. **By resolving issues:** Typically this is done in the form of either 37 | demonstrating that the issue reported is not a problem after all, or more often, 38 | by opening a pull request that fixes the underlying problem, in a concrete and 39 | reviewable manner. 40 | 41 | **Anybody can participate in any stage of contribution**. We urge you to participate in the discussion around bugs and 42 | participate in reviewing PRs. 43 | 44 | ### Submitting a bug report 45 | 46 | When filing a new bug report in the issue tracker, you will be presented with a basic form to fill out. 47 | 48 | If you believe that you have uncovered a bug, please fill out the form to the best of your ability. Do not worry if you 49 | cannot answer every detail, just fill in what you can. Contributors will ask follow-up questions if something is 50 | unclear. 51 | 52 | The most important pieces of information we need in a bug report are: 53 | 54 | - The Shadow Reth version you are on (and that it is up to date) 55 | - The platform you are on (Windows, macOS, an M1 Mac or Linux) 56 | - Code snippets if this is happening in relation to testing or building code 57 | - Concrete steps to reproduce the bug 58 | 59 | In order to rule out the possibility of the bug being in your project, the code snippets should be as minimal as 60 | possible. It is better if you can reproduce the bug with a small snippet as opposed to an entire project! 61 | 62 | See [this guide][mcve] on how to create a minimal, complete, and verifiable example. 63 | 64 | ### Submitting a feature request 65 | 66 | When adding a feature request in the issue tracker, you will be presented with a basic form to fill out. 67 | 68 | Please include as detailed of an explanation as possible of the feature you would like, adding additional context if 69 | necessary. 70 | 71 | If you have examples of other tools that have the feature you are requesting, please include them as well. 72 | 73 | ### Resolving an issue 74 | 75 | Pull requests are the way concrete changes are made to the code, documentation, and dependencies of Shadow Reth. 76 | 77 | Even tiny pull requests, like fixing wording, are greatly appreciated. Before making a large change, it is usually a 78 | good idea to first open an issue describing the change to solicit feedback and guidance. This will increase the 79 | likelihood of the PR getting merged. 80 | 81 | #### Adding tests 82 | 83 | If the change being proposed alters code, it is either adding new functionality to Shadow Reth, or fixing existing, broken 84 | functionality. 85 | In both of these cases, the pull request should include one or more tests to ensure that Shadow Reth does not regress in the 86 | future. 87 | 88 | Types of tests include: 89 | 90 | - **Unit tests**: Functions which have very specific tasks should be unit tested. 91 | - **Integration tests**: For general purpose, far reaching functionality, 92 | integration tests should be added. The best way to add a new integration test is to look at existing ones and follow 93 | the style. 94 | 95 | #### Running Individual tests 96 | 97 | By default, `cargo test` does not select any packages, in order to run individual tests by name ( 98 | e.g. `cargo test mytest`) navigate to the directory of that file (e.g. `shadow-reth/crates/exex`) or use 99 | the `-p ` option to run specific tests of a crate from anywhere in the 100 | workspace (`cargo test -p reth-rpc mytest`). 101 | 102 | See also [cargo-test](https://doc.rust-lang.org/cargo/commands/cargo-test.html) for more information on running tests. 103 | 104 | #### Commits 105 | 106 | It is a recommended best practice to keep your changes as logically grouped as possible within individual commits. There 107 | is no limit to the number of commits any single pull request may have, and many contributors find it easier to review 108 | changes that are split across multiple commits. 109 | 110 | That said, if you have a number of commits that are "checkpoints" and don't represent a single logical change, please 111 | squash those together. 112 | 113 | #### Opening the pull request 114 | 115 | From within GitHub, opening a new pull request will present you with a template that should be filled out. Please try 116 | your best at filling out the details, but feel free to skip parts if you're not sure what to put. 117 | 118 | #### Discuss and update 119 | 120 | You will probably get feedback or requests for changes to your pull request. 121 | This is a big part of the submission process, so don't be discouraged! Some contributors may sign off on the pull 122 | request right away, others may have more detailed comments or feedback. This is a necessary part of the process in order 123 | to evaluate whether the changes are correct and necessary. 124 | 125 | **Any community member can review a PR, so you might get conflicting feedback**. Keep an eye out for comments from code 126 | owners to provide guidance on conflicting feedback. 127 | 128 | #### Reviewing pull requests 129 | 130 | **Any community member is welcome to review any pull request**. 131 | 132 | All contributors who choose to review and provide feedback on pull requests have a responsibility to both the project 133 | and individual making the contribution. Reviews and feedback must be helpful, insightful, and geared towards improving 134 | the contribution as opposed to simply blocking it. If there are reasons why you feel the PR should not be merged, 135 | explain what those are. Do not expect to be able to block a PR from advancing simply because you say "no" without giving 136 | an explanation. Be open to having your mind changed. Be open to working _with_ the contributor to make the pull request 137 | better. 138 | 139 | Reviews that are dismissive or disrespectful of the contributor or any other reviewers are strictly counter to 140 | the [Code of Conduct][coc-header]. 141 | 142 | When reviewing a pull request, the primary goals are for the codebase to improve and for the person submitting the 143 | request to succeed. **Even if a pull request is not merged, the submitter should come away from the experience feeling 144 | like their effort was not unappreciated**. Every PR from a new contributor is an opportunity to grow the community. 145 | 146 | ##### Review a bit at a time 147 | 148 | Do not overwhelm new contributors. 149 | 150 | It is tempting to micro-optimize and make everything about relative performance, perfect grammar, or exact style 151 | matches. Do not succumb to that temptation. 152 | 153 | Focus first on the most significant aspects of the change: 154 | 155 | 1. Does this change make sense for Shadow Reth? 156 | 2. Does this change make Shadow Reth better, even if only incrementally? 157 | 3. Are there clear bugs or larger scale issues that need attending? 158 | 4. Are the commit messages readable and correct? If it contains a breaking change, is it clear enough? 159 | 160 | Note that only **incremental** improvement is needed to land a PR. This means that the PR does not need to be perfect, 161 | only better than the status quo. Follow-up PRs may be opened to continue iterating. 162 | 163 | When changes are necessary, _request_ them, do not _demand_ them, and **do not assume that the submitter already knows 164 | how to add a test or run a benchmark**. 165 | 166 | Specific performance optimization techniques, coding styles and conventions change over time. The first impression you 167 | give to a new contributor never does. 168 | 169 | Nits (requests for small changes that are not essential) are fine, but try to avoid stalling the pull request. Most nits 170 | can typically be fixed by the Shadow Reth maintainers merging the pull request, but they can also be an opportunity for the 171 | contributor to learn a bit more about the project. 172 | 173 | It is always good to clearly indicate nits when you comment, 174 | e.g.: `Nit: change foo() to bar(). But this is not blocking`. 175 | 176 | If your comments were addressed but were not folded after new commits, or if they proved to be mistaken, 177 | please, [hide them][hiding-a-comment] with the appropriate reason to keep the conversation flow concise and relevant. 178 | 179 | ##### Be aware of the person behind the code 180 | 181 | Be aware that _how_ you communicate requests and reviews in your feedback can have a significant impact on the success 182 | of the pull request. Yes, we may merge a particular change that makes Shadow Reth better, but the individual might just not 183 | want to have anything to do with Shadow Reth ever again. The goal is not just having good code. 184 | 185 | ##### Abandoned or stale pull requests 186 | 187 | If a pull request appears to be abandoned or stalled, it is polite to first check with the contributor to see if they 188 | intend to continue the work before checking if they would mind if you took it over (especially if it just has nits 189 | left). When doing so, it is courteous to give the original contributor credit for the work they started, either by 190 | preserving their name and e-mail address in the commit log, or by using the `Author: ` or `Co-authored-by: ` metadata 191 | tag in the commits. 192 | 193 | _Adapted from the [Reth Contributing Guide](https://raw.githubusercontent.com/paradigmxyz/reth/main/CONTRIBUTING.md)_. 194 | 195 | [rust-coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md 196 | 197 | [coc-header]: #code-of-conduct 198 | 199 | [reth-book]: https://github.com/paradigmxyz/reth/tree/main/book 200 | 201 | [mcve]: https://stackoverflow.com/help/mcve 202 | 203 | [hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["bin/shadow-reth", "crates/common", "crates/exex", "crates/rpc"] 3 | default-members = ["bin/shadow-reth"] 4 | 5 | # Explicitly set the resolver to version 2, which is the default for packages with edition >= 2021 6 | # https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html 7 | resolver = "2" 8 | 9 | [workspace.package] 10 | description = "A single-node implementation of a Shadow RPC on top of Reth, utilizing ExEx and custom RPC methods." 11 | version = "0.1.0" 12 | edition = "2021" 13 | homepage = "https://app.shadow.xyz" 14 | repository = "https://github.com/shadow-hq/shadow-reth" 15 | keywords = ["ethereum", "web3", "evm", "exex", "reth", "shadow"] 16 | exclude = [".github/"] 17 | rust-version = "1.76" 18 | license = "MIT OR Apache-2.0" 19 | 20 | [profile.release] 21 | lto = "thin" 22 | strip = "debuginfo" 23 | 24 | # Like release, but with full debug symbols. Useful for e.g. `perf`. 25 | [profile.debug-fast] 26 | inherits = "release" 27 | strip = "none" 28 | debug = true 29 | 30 | [profile.maxperf] 31 | inherits = "release" 32 | lto = "fat" 33 | codegen-units = 1 34 | incremental = false 35 | 36 | [workspace.lints] 37 | rust.missing_debug_implementations = "warn" 38 | rust.missing_docs = "warn" 39 | rust.unreachable_pub = "warn" 40 | rust.unused_must_use = "deny" 41 | rust.rust_2018_idioms = "deny" 42 | rustdoc.all = "warn" 43 | 44 | [workspace.lints.clippy] 45 | # These are some of clippy's nursery (i.e., experimental) lints that we like. 46 | # By default, nursery lints are allowed. Some of the lints below have made good 47 | # suggestions which we fixed. The others didn't have any findings, so we can 48 | # assume they don't have that many false positives. Let's enable them to 49 | # prevent future problems. 50 | branches_sharing_code = "warn" 51 | clear_with_drain = "warn" 52 | derive_partial_eq_without_eq = "warn" 53 | empty_line_after_outer_attr = "warn" 54 | equatable_if_let = "warn" 55 | imprecise_flops = "warn" 56 | iter_on_empty_collections = "warn" 57 | iter_with_drain = "warn" 58 | large_stack_frames = "warn" 59 | manual_clamp = "warn" 60 | mutex_integer = "warn" 61 | needless_pass_by_ref_mut = "warn" 62 | nonstandard_macro_braces = "warn" 63 | or_fun_call = "warn" 64 | path_buf_push_overwrite = "warn" 65 | read_zero_byte_vec = "warn" 66 | redundant_clone = "warn" 67 | suboptimal_flops = "warn" 68 | suspicious_operation_groupings = "warn" 69 | trailing_empty_array = "warn" 70 | trait_duplication_in_bounds = "warn" 71 | transmute_undefined_repr = "warn" 72 | trivial_regex = "warn" 73 | tuple_array_conversions = "warn" 74 | uninhabited_references = "warn" 75 | unused_peekable = "warn" 76 | unused_rounding = "warn" 77 | useless_let_if_seq = "warn" 78 | 79 | # These are nursery lints which have findings. Allow them for now. Some are not 80 | # quite mature enough for use in our codebase and some we don't really want. 81 | # Explicitly listing should make it easier to fix in the future. 82 | as_ptr_cast_mut = "allow" 83 | cognitive_complexity = "allow" 84 | collection_is_never_read = "allow" 85 | debug_assert_with_mut_call = "allow" 86 | empty_line_after_doc_comments = "allow" 87 | fallible_impl_from = "allow" 88 | future_not_send = "allow" 89 | iter_on_single_items = "allow" 90 | missing_const_for_fn = "allow" 91 | needless_collect = "allow" 92 | non_send_fields_in_send_ty = "allow" 93 | option_if_let_else = "allow" 94 | redundant_pub_crate = "allow" 95 | significant_drop_in_scrutinee = "allow" 96 | significant_drop_tightening = "allow" 97 | string_lit_as_bytes = "allow" 98 | type_repetition_in_bounds = "allow" 99 | unnecessary_struct_initialization = "allow" 100 | use_self = "allow" 101 | 102 | [workspace.dependencies] 103 | # Shadow 104 | shadow-reth = { path = "bin/shadow-reth" } 105 | shadow-reth-exex = { path = "crates/exex" } 106 | shadow-reth-rpc = { path = "crates/rpc" } 107 | shadow-reth-common = { path = "crates/common" } 108 | 109 | # Reth 110 | reth = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 111 | reth-exex = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 112 | reth-node-api = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 113 | reth-node-builder = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 114 | reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 115 | reth-primitives = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 116 | reth-tracing = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 117 | reth-evm = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 118 | reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 119 | reth-provider = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 120 | reth-revm = { git = "https://github.com/paradigmxyz/reth.git", rev = "d777d5f" } 121 | 122 | # Crates.io 123 | eyre = "0.6.12" 124 | tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] } 125 | futures = "0.3.30" 126 | tracing = "0.1.40" 127 | serde = "1.0.203" 128 | serde_json = "1.0.117" 129 | 130 | # RPC 131 | jsonrpsee = "0.22.5" 132 | sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio"] } 133 | 134 | [patch.crates-io] 135 | revm = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } 136 | revm-interpreter = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } 137 | revm-precompile = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } 138 | revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "a28a543" } 139 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 Shadow Reth Contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Shadow Reth Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shadow-reth 2 | 3 | A single-node implementation of a Shadow RPC on top of Reth. 4 | 5 | `shadow-reth` contains a series of [Reth](https://github.com/paradigmxyz/reth) modifications that enable you to generate shadow events via [Execution Extensions](https://www.paradigm.xyz/2024/05/reth-exex), and retrieve them easily with a custom RPC Extension. 6 | 7 | See our [blog post](https://blog.shadow.xyz/shadow-reth/) for more information. 8 | 9 | ## Getting Started 10 | 11 | You can get started with running `shadow-reth` in four steps: 12 | 13 | 1. Clone this repository 14 | 2. Generate a shadow node configuration using any external tool 15 | 3. Build and install the `shadow-reth` binary 16 | 4. Fetch shadow events via the `shadow_getLogs` JSON-RPC endpoint 17 | 18 | ### Step 1: Clone this repository 19 | 20 | ```bash 21 | git clone https://github.com/shadow-hq/shadow-reth 22 | cd shadow-reth 23 | ``` 24 | 25 | ### Step 2: Configure your shadow node 26 | 27 | To quickly get started, you can use the example `shadow.json` file in this repository. It contains recompiled bytecode for the WETH contract with an added `ShadowTransfer` event. 28 | 29 | ```bash 30 | cp shadow.json.example shadow.json 31 | ``` 32 | 33 | Otherwise, see the Shadow Configuration section below for detailed instructions on how to configure your shadow node. 34 | 35 | ### Step 3: Build and install `shadow-reth` 36 | 37 | ```bash 38 | cargo install --locked --path bin/shadow-reth --bin shadow-reth 39 | 40 | # start your shadow-reth node 41 | shadow-reth node [RETH OPTIONS] 42 | ``` 43 | 44 | ### Step 4: Fetch shadow events via `shadow_getLogs` 45 | 46 | ```bash 47 | curl http://127.0.0.1:8545 \ 48 | -X POST \ 49 | -H "Content-Type: application/json" \ 50 | --data '{"method":"shadow_getLogs","params":[{"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}],"id":1,"jsonrpc":"2.0"}' \ 51 | | json_pp 52 | ``` 53 | 54 | ## Shadow Configuration 55 | 56 | ### With Shadow 57 | 58 | 1. Navigate to https://app.shadow.xyz 59 | 2. Search for the contract you wish to modify using the search bar at the top of the webpage. 60 | 61 |
62 | View Screenshot 63 | preview 64 |
65 | 66 | 3. Open the contract in the editor and make your changes. When you’re satisfied with your shadow contract, use the compile button, and then the deploy button. 67 | 68 |
69 | View Screenshots 70 | preview 71 | preview 72 |
73 | 74 | 4. On the home screen, click on the “Reth ExEx” tab, then click “Download JSON”. 75 | 76 |
77 | View Screenshots 78 | preview 79 |
80 | 81 | ### With Foundry 82 | 83 | 1. Clone the contract source from a verified contract on Etherscan. 84 | 85 | ```bash 86 | forge clone 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 87 | ``` 88 | 89 | 2. Modify the cloned source code 90 | 3. Compile your modified contract, and copy the deployed bytecode from the compiler output: 91 | 92 | ```bash 93 | # compile your changes 94 | forge build 95 | 96 | # get the deployed bytecode 97 | CONTRACT_SRC_FILE=Contract.sol 98 | CONTRACT_NAME=WETH9 99 | grep '"deployedBytecode":' out/$CONTRACT_SRC_FILE/$CONTRACT_NAME.json | sed -n 's/.*"object": *"\([^"]*\)".*/\1/p' 100 | ``` 101 | 102 | 4. Add the contract address and shadow bytecode to `shadow.json`: 103 | 104 | ```json 105 | { 106 | "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "0x60606040..." 107 | } 108 | ``` 109 | 110 | ### How does it work? 111 | 112 | Here's how it works at a high level: 113 | 114 | 1. Generate shadow bytecode using any external tool, such as [foundry](https://github.com/foundry-rs/foundry). This bytecode is then added to `shadow.json`, which simply maps contract addresses to their shadow bytecode. 115 | 116 | For example, if you wanted to shadow [Wrapped Ether](https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2), you would add the following to `shadow.json`: 117 | 118 | ```json 119 | { 120 | "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "0x60606040..." 121 | } 122 | ``` 123 | 124 | 2. Run `shadow-reth`, exactly like you would start and run a normal [Reth](https://github.com/paradigmxyz/reth) node. When a block is committed to the chain, an `ExExNotification` is emitted and handled by [ShadowExEx](./crates/exex), which re-executes each transaction in the block, using a `ShadowDatabase` (which implements `revm::Database`), with the shadow bytecode injected as defined in `shadow.json`. In addition to this, the `base_fee_per_gas` is set to 0, allowing shadow contracts to perform arbitrary computations without worrying about gas costs. Events emitted by shadow contracts are then stored in a sqlite database in revm's `datadir`. 125 | 126 | > Note: All log index fields (`block_log_index`, `transaction_log_index`) _will include_ shadow events. As a result, shadow events will be interleaved with canonical events in the same block, and log indices _will not_ match the canonical chain. 127 | 128 | 3. A namespaced `shadow` JSON-RPC (see [ShadowRpc](./crates/rpc)) is exposed, which allows you to interact with your shadowed contracts. Currently, only `shadow_getLogs` is implemented, which allows you to retrieve Shadow Events emitted by your shadow contracts. 129 | 130 | > Note: for this example, we've added a simple `ShadowTransfer(address,address,uint256)` event to the Wrapped Ether shadow bytecode. This event has the signature `0xe7742d659c2c3c18fba9c357096ed6d568223cb89064e8bc947b709cba2a6ab7`. 131 | 132 | ```bash 133 | curl http://127.0.0.1:8545 \ 134 | -X POST \ 135 | -H "Content-Type: application/json" \ 136 | --data '{"method":"shadow_getLogs","params":[{"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}],"id":1,"jsonrpc":"2.0"}' \ 137 | | json_pp 138 | ``` 139 | 140 |
141 | Expand response 142 | 143 | ```json 144 | { 145 | "jsonrpc": "2.0", 146 | "result": [ 147 | { 148 | "address" : "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 149 | "blockHash" : "0xe54e22affd13be3e77449a5af5c29d2aee11ffb4f3da44845544f4d55de24e8c", 150 | "blockNumber" : "00000000012fd986", 151 | "data" : "0x000000000000000000000000000000000000000000000000052a871b93874afb", 152 | "logIndex" : "1", 153 | "removed" : false, 154 | "topics" : [ 155 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 156 | "0x000000000000000000000000961ec3bb28c9e98a040c4bded38917aa96b791be", 157 | "0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", 158 | null 159 | ], 160 | "transactionHash" : "0xa92037f3e25559e6ccdfdd8695286be525eb7d36f194176a4d577e6ef4409545", 161 | "transactionIndex" : "123" 162 | }, 163 | { 164 | "address" : "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 165 | "blockHash" : "0xe54e22affd13be3e77449a5af5c29d2aee11ffb4f3da44845544f4d55de24e8c", 166 | "blockNumber" : "00000000012fd986", 167 | "data" : "0x000000000000000000000000000000000000000000000000052a871b93874afb", 168 | "logIndex" : "2", 169 | "removed" : false, 170 | "topics" : [ 171 | "0xe7742d659c2c3c18fba9c357096ed6d568223cb89064e8bc947b709cba2a6ab7", 172 | "0x000000000000000000000000961ec3bb28c9e98a040c4bded38917aa96b791be", 173 | "0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", 174 | null 175 | ], 176 | "transactionHash" : "0xa92037f3e25559e6ccdfdd8695286be525eb7d36f194176a4d577e6ef4409545", 177 | "transactionIndex" : "123" 178 | }, 179 | ... 180 | ] 181 | } 182 | ``` 183 | 184 |
185 | 186 | As a result, `shadow-reth` allows you to run a trustless, fully open-source version of a shadow node. 187 | 188 | ## Limitations 189 | 190 | - Gas limits: `shadow-reth` does not override gas limits when re-executing a block with `ShadowExecutor` for data consistency reasons. Transactions may fail if they run out of gas during shadow re-execution, and no shadow events will be emitted for that transaction. 191 | - Backfilling: `shadow-reth` does not backfill shadow events. If you start running `shadow-reth` on a synced Reth node, `shadow-reth` will only generate shadow events for blocks that have been processed since `shadow-reth` was started. If you want historical shadow events, you’ll need to re-sync your Reth node from genesis. We’re working closely with the Reth team to improve this. 192 | - Decoding: `shadow-reth` is designed to be analogous to a regular node, which doesn’t include event decoding. If you want to decode shadow events, we recommend polling the `shadow_getLogs` endpoint in a separate process. 193 | - Websockets: Shadow events will not be published over `eth_subscribe` websocket subscriptions. 194 | 195 | ## Getting Help 196 | 197 | If you have any questions, first see if the answer to your question can be found in the [reth book](https://paradigmxyz.github.io/reth/) 198 | 199 | If the answer is not there and is specific to `shadow-reth`, you can: 200 | 201 | - Join the [Telegram](https://t.me/shadow_devs) to get help, or 202 | - Open an issue with the [bug](https://github.com/shadow-hq/shadow-reth/issues/new?assignees=&template=bug.yml) 203 | 204 | ## Contributing 205 | 206 | See our [contributing guidelines](./CONTRIBUTING.md). 207 | 208 | ## Security 209 | 210 | This code has not been audited, and should not be used in any production systems. 211 | 212 | ## Acknowledgements 213 | 214 | `shadow-reth` wouldn't be possible without the hard work of the following projects: 215 | 216 | - [Reth](https://github.com/paradigmxyz/reth): The foundation of `shadow-reth`, an Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient. 217 | - [Revm](https://github.com/bluealloy/revm): Revm is an EVM written in Rust that is focused on speed and simplicity. Revm is the backbone of `shadow-reth`’s `ShadowExecutor`, as well as Reth itself. 218 | -------------------------------------------------------------------------------- /bin/shadow-reth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shadow-reth" 3 | description = "A single-node implementation of a Shadow RPC on top of Reth, utilizing ExEx and custom RPC methods." 4 | version.workspace = true 5 | edition.workspace = true 6 | rust-version.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | exclude.workspace = true 11 | license.workspace = true 12 | 13 | [lints] 14 | workspace = true 15 | 16 | [dependencies] 17 | # Shadow 18 | shadow-reth-exex.workspace = true 19 | shadow-reth-rpc.workspace = true 20 | 21 | # Reth 22 | reth.workspace = true 23 | reth-node-ethereum.workspace = true 24 | 25 | # Crates 26 | eyre.workspace = true 27 | tokio.workspace = true 28 | -------------------------------------------------------------------------------- /bin/shadow-reth/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Shadow Reth: An open-source reth node with support for shadow bytecode. 2 | //! 3 | //! Works by using [`shadow-reth-exex`] to replay canonical transactions with shadow bytecode, 4 | //! and [`shadow-reth-rpc`] to provide an RPC interface for interacting with shadow data. 5 | 6 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 7 | 8 | use eyre::Result; 9 | use reth_node_ethereum::EthereumNode; 10 | use shadow_reth_exex::ShadowExEx; 11 | use shadow_reth_rpc::ShadowRpc; 12 | 13 | fn main() -> Result<()> { 14 | // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. 15 | if std::env::var_os("RUST_BACKTRACE").is_none() { 16 | std::env::set_var("RUST_BACKTRACE", "1"); 17 | } 18 | 19 | reth::cli::Cli::parse_args().run(|builder, _| async move { 20 | let shadow_db_path = builder.data_dir().db().join("shadow.db"); 21 | let (indexed_block_hash_sender, indexed_block_hash_receiver) = 22 | tokio::sync::broadcast::channel(4096); 23 | 24 | // Start reth w/ the shadow exex. 25 | let handle = builder 26 | .node(EthereumNode::default()) 27 | .install_exex("ShadowExEx", move |ctx| ShadowExEx::init(ctx, indexed_block_hash_sender)) 28 | .extend_rpc_modules(move |ctx| { 29 | ShadowRpc::init(ctx, shadow_db_path, indexed_block_hash_receiver) 30 | }) 31 | .launch() 32 | .await?; 33 | 34 | // Wait for the node to exit. 35 | handle.wait_for_node_exit().await?; 36 | 37 | Ok(()) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Shadow Reth Core Contributors"] 3 | language = "en" 4 | multilingual = false 5 | src = "book" 6 | title = "Shadow Reth Book" 7 | description = "A book on all things Shadow Reth" 8 | 9 | [output.html] 10 | theme = "book/theme" 11 | git-repository-url = "https://github.com/shadow-hq/shadow-reth" 12 | default-theme = "ayu" 13 | no-section-label = true 14 | 15 | [output.html.fold] 16 | enable = true 17 | level = 1 18 | 19 | [build] 20 | build-dir = "target/book" 21 | 22 | [preprocessor.template] 23 | before = ["links"] 24 | 25 | [preprocessor.index] 26 | 27 | [preprocessor.links] 28 | -------------------------------------------------------------------------------- /crates/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shadow-reth-common" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | keywords.workspace = true 9 | exclude.workspace = true 10 | license.workspace = true 11 | 12 | [lints] 13 | workspace = true 14 | 15 | [dependencies] 16 | # Reth 17 | reth-primitives.workspace = true 18 | reth-tracing.workspace = true 19 | 20 | # Crates 21 | eyre.workspace = true 22 | sqlx.workspace = true 23 | -------------------------------------------------------------------------------- /crates/common/src/db.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use eyre::Result; 4 | use reth_primitives::BlockHash; 5 | use reth_tracing::tracing::debug; 6 | use sqlx::{ 7 | sqlite::{SqliteConnectOptions, SqlitePoolOptions}, 8 | Pool, Sqlite, 9 | }; 10 | 11 | use crate::ShadowLog; 12 | 13 | /// Wrapper type around a SQLite connection pool. 14 | #[derive(Clone, Debug)] 15 | pub struct ShadowSqliteDb { 16 | /// Connection pool. 17 | pub pool: Pool, 18 | } 19 | 20 | impl ShadowSqliteDb { 21 | /// Creates a new instance. 22 | pub async fn new(db_path: &str) -> Result { 23 | let pool = SqlitePoolOptions::new() 24 | .connect_with(SqliteConnectOptions::from_str(db_path)?.create_if_missing(true)) 25 | .await?; 26 | create_tables(&pool).await?; 27 | create_indices(&pool).await?; 28 | 29 | Ok(Self { pool }) 30 | } 31 | 32 | #[allow(clippy::format_in_format_args)] 33 | /// Bulk insert a list of [`ShadowLog`] instances into the `shadow_log` table. 34 | /// 35 | /// Note: using format here over bind because input is trusted, and bind was causing 36 | /// borrow checker headaches. 37 | pub async fn bulk_insert_into_shadow_log_table( 38 | &self, 39 | logs: Vec, 40 | ) -> Result<(), sqlx::Error> { 41 | let start_time = std::time::Instant::now(); 42 | let mut query = "INSERT INTO shadow_logs ( 43 | block_number, 44 | block_hash, 45 | block_timestamp, 46 | transaction_index, 47 | transaction_hash, 48 | block_log_index, 49 | transaction_log_index, 50 | address, 51 | data, 52 | topic_0, 53 | topic_1, 54 | topic_2, 55 | topic_3, 56 | removed, 57 | created_at, 58 | updated_at 59 | ) VALUES " 60 | .to_string(); 61 | 62 | let logs_len = logs.len(); 63 | logs.iter().enumerate().for_each(|(i, log)| { 64 | query.push_str(&format!( 65 | "({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, date(), date())", 66 | log.block_number, 67 | format!("X'{}'", &log.block_hash[2..]), 68 | log.block_timestamp, 69 | log.transaction_index, 70 | format!("X'{}'", &log.transaction_hash[2..]), 71 | log.block_log_index, 72 | log.transaction_log_index, 73 | format!("X'{}'", &log.address[2..]), 74 | log.data.clone().map_or("NULL".to_string(), |d| format!("X'{}'", &d[2..])), 75 | log.topic_0.clone().map_or("NULL".to_string(), |t| format!("X'{}'", &t[2..])), 76 | log.topic_1.clone().map_or("NULL".to_string(), |t| format!("X'{}'", &t[2..])), 77 | log.topic_2.clone().map_or("NULL".to_string(), |t| format!("X'{}'", &t[2..])), 78 | log.topic_3.clone().map_or("NULL".to_string(), |t| format!("X'{}'", &t[2..])), 79 | log.removed 80 | )); 81 | if i < logs_len - 1 { 82 | query.push_str(", "); 83 | } 84 | }); 85 | 86 | let _ = sqlx::query(&query).execute(&self.pool).await?; 87 | debug!("Inserted {} shadow logs in {:?}", logs_len, start_time.elapsed()); 88 | Ok(()) 89 | } 90 | 91 | /// Marks all logs with the given `block_hash` as removed. 92 | /// 93 | /// This is used to invalid all logs in a block when a reorg happens. 94 | pub async fn handle_block_reorg(&self, block_hash: BlockHash) -> Result<()> { 95 | let start_time = std::time::Instant::now(); 96 | let _ = sqlx::query(&format!( 97 | "UPDATE shadow_logs SET removed = true WHERE block_hash = X'{block_hash:x}'", 98 | )) 99 | .execute(&self.pool) 100 | .await?; 101 | debug!("Invalidated block '{block_hash}' in {:?}", start_time.elapsed()); 102 | Ok(()) 103 | } 104 | } 105 | 106 | async fn create_tables(pool: &Pool) -> Result<(), sqlx::Error> { 107 | // Since BIGINT in SQLite is actually an i64, we're storing the unsigned 108 | // values as text instead. The values for these fields will be converted 109 | // into their u64 counterparts as they are returned from the database. 110 | let sql = r#" 111 | CREATE TABLE IF NOT EXISTS shadow_logs( 112 | block_number text not null, 113 | block_hash varchar(66) not null, 114 | block_timestamp text not null, 115 | transaction_index text not null, 116 | transaction_hash varchar(66) not null, 117 | block_log_index text not null, 118 | transaction_log_index text not null, 119 | address varchar(42) not null, 120 | removed boolean not null, 121 | data text, 122 | topic_0 varchar(66), 123 | topic_1 varchar(66), 124 | topic_2 varchar(66), 125 | topic_3 varchar(66), 126 | created_at datetime, 127 | updated_at datetime 128 | ) 129 | "#; 130 | 131 | let _ = sqlx::query(sql).execute(pool).await?; 132 | Ok(()) 133 | } 134 | 135 | async fn create_indices(pool: &Pool) -> Result<(), sqlx::Error> { 136 | let sql = r#" 137 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_address ON shadow_logs (address); 138 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_block_number ON shadow_logs (block_number); 139 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_block_hash ON shadow_logs (block_hash); 140 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_topic_0 ON shadow_logs (topic_0); 141 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_topic_1 ON shadow_logs (topic_1); 142 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_topic_2 ON shadow_logs (topic_2); 143 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_topic_3 ON shadow_logs (topic_3); 144 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_transaction_hash ON shadow_logs (transaction_hash); 145 | CREATE INDEX IF NOT EXISTS idx_shadow_logs_removed ON shadow_logs (removed); 146 | "#; 147 | 148 | let _ = sqlx::query(sql).execute(pool).await?; 149 | Ok(()) 150 | } 151 | -------------------------------------------------------------------------------- /crates/common/src/hex.rs: -------------------------------------------------------------------------------- 1 | use reth_primitives::{Address, Bloom, Bytes, B256, B64}; 2 | 3 | /// A trait for converting primitives to lowercase hexadecimal strings. 4 | pub trait ToLowerHex { 5 | /// Converts the value to a lowercase hexadecimal string. 6 | /// 7 | /// ``` 8 | /// use reth_primitives::Address; 9 | /// use shadow_reth_common::ToLowerHex; 10 | /// 11 | /// let value = Address::ZERO; 12 | /// assert_eq!(value.to_lower_hex(), "0x0000000000000000000000000000000000000000"); 13 | /// ``` 14 | fn to_lower_hex(&self) -> String; 15 | } 16 | 17 | impl ToLowerHex for B256 { 18 | fn to_lower_hex(&self) -> String { 19 | format!("{self:#x}") 20 | } 21 | } 22 | 23 | impl ToLowerHex for Address { 24 | fn to_lower_hex(&self) -> String { 25 | format!("{self:#x}") 26 | } 27 | } 28 | 29 | impl ToLowerHex for B64 { 30 | fn to_lower_hex(&self) -> String { 31 | format!("{self:#x}") 32 | } 33 | } 34 | 35 | impl ToLowerHex for Bloom { 36 | fn to_lower_hex(&self) -> String { 37 | format!("{self:#x}") 38 | } 39 | } 40 | 41 | impl ToLowerHex for Bytes { 42 | fn to_lower_hex(&self) -> String { 43 | format!("{self:#x}") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Common utilities and functions used throughout [`shadow-reth`]. 2 | 3 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 4 | 5 | mod db; 6 | mod hex; 7 | mod types; 8 | 9 | // re-exports 10 | pub use db::*; 11 | pub use hex::*; 12 | pub use types::*; 13 | -------------------------------------------------------------------------------- /crates/common/src/types.rs: -------------------------------------------------------------------------------- 1 | /// A shadow log entry. 2 | #[derive(Debug, Clone)] 3 | pub struct ShadowLog { 4 | /// Contract address from which the log originated. 5 | pub address: String, 6 | /// Hash of block from which the log originated. 7 | pub block_hash: String, 8 | /// Integer of the log index in the containing block. 9 | pub block_log_index: u64, 10 | /// Block number from which the log originated. 11 | pub block_number: u64, 12 | /// Timestamp of block from which the log originated. 13 | pub block_timestamp: u64, 14 | /// Integer of the transaction index position from which the log originated. 15 | pub transaction_index: u64, 16 | /// Hash of transaction from which the log originated. 17 | pub transaction_hash: String, 18 | /// Integer of the log index in the containing transaction. 19 | pub transaction_log_index: u64, 20 | /// Indicates whether the log has been removed from the canonical chain. 21 | pub removed: bool, 22 | /// Contains one or more 32-byte non-indexed arguments of the log. 23 | pub data: Option, 24 | /// Topic 0. 25 | pub topic_0: Option, 26 | /// Topic 1. 27 | pub topic_1: Option, 28 | /// Topic 2. 29 | pub topic_2: Option, 30 | /// Topic 3. 31 | pub topic_3: Option, 32 | } 33 | -------------------------------------------------------------------------------- /crates/exex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shadow-reth-exex" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | keywords.workspace = true 9 | exclude.workspace = true 10 | license.workspace = true 11 | 12 | [lints] 13 | workspace = true 14 | 15 | [dependencies] 16 | # Shadow 17 | shadow-reth-common.workspace = true 18 | 19 | # Reth 20 | reth-evm-ethereum.workspace = true 21 | reth-exex.workspace = true 22 | reth-node-api.workspace = true 23 | reth-primitives.workspace = true 24 | reth-provider.workspace = true 25 | reth-tracing.workspace = true 26 | reth-revm.workspace = true 27 | 28 | # Crates 29 | eyre.workspace = true 30 | tokio.workspace = true 31 | futures.workspace = true 32 | serde_json.workspace = true 33 | -------------------------------------------------------------------------------- /crates/exex/README.md: -------------------------------------------------------------------------------- 1 | # shadow-exex 2 | 3 | The Shadow Execution Extension (ExEx) is a [Reth execution extension](https://www.paradigm.xyz/2024/05/reth-exex) that generates information about shadowed contracts and persists it in a SQLite database. 4 | 5 | ## How does it work? 6 | 7 | At a high level, the Shadow ExEx works as follows: 8 | 9 | ### Chain Committed 10 | 11 | When blocks are committed to the chain, Reth emits `ExExNotification::ChainCommitted` for each transaction in the block. This notification contains the entire chain state, along with helpful block and transaction information such as `SealedBlockWithSenders`. `ShadowExEx` needs to re-execute each transaction in the block using `ShadowDatabase` (which implements `revm::Database`). To do this, we use `ShadowExecutor`, a simple block executor using revm, which will execute each transaction in a given block and commits changes to `ShadowDatabase`. When block execution is complete, shadow logs can be recovered from the `ExecutedBlock`, and stored in the SQLite database. 12 | 13 | #### ShadowDatabase 14 | 15 | `ShadowDatabase` is a simple implementation of `revm::Database` that stores the state of shadow contracts in a SQLite database. It is used by `ShadowExecutor` to serve as a `revm::Database` implementation, which also handles shadowing contract bytecode where applicable. 16 | 17 |
18 | Expand code 19 | 20 | ```rust 21 | impl DatabaseRef for ShadowDatabase { 22 | /// Retrieves basic account information for a given address. 23 | /// 24 | /// Returns `Ok` with `Some(AccountInfo)` if the account exists, 25 | /// `None` if it doesn't, or an error if encountered. 26 | fn basic_ref(&self, address: Address) -> Result, Self::Error> { 27 | Ok(self.db.basic_account(address)?.map(|account| AccountInfo { 28 | balance: account.balance, 29 | nonce: account.nonce, 30 | code_hash: self 31 | .shadow 32 | .code_hash(&address) // Check if the address is a shadow contract, and use that code hash 33 | .unwrap_or_else(|| account.bytecode_hash.unwrap_or(KECCAK_EMPTY)), 34 | code: self.shadow.code(&address), 35 | })) 36 | } 37 | 38 | /// Retrieves the bytecode associated with a given code hash. 39 | /// 40 | /// Returns `Ok` with the bytecode if found, or the default bytecode otherwise. 41 | fn code_by_hash_ref(&self, code_hash: B256) -> Result { 42 | Ok(self.shadow.code_by_hash(&code_hash).unwrap_or_else(|| { 43 | self.bytecode_by_hash(code_hash).ok().flatten().unwrap_or_default().0 44 | })) 45 | } 46 | 47 | ... 48 | } 49 | ``` 50 | 51 |
52 | 53 | #### ShadowExecutor 54 | 55 | `ShadowExecutor` is a simple block executor using revm, which will execute each transaction in a given block and commit changes to `ShadowDatabase`. It is used by `ShadowExEx` to re-execute transactions in a block and store shadow logs in the SQLite database. 56 | 57 | `ShadowExecutor` uses `transact_preverified` to execute transactions in a block, since: 58 | 59 | 1. The block has already been verified by the chain, so we don't need to re-verify it. 60 | 2. We're modifying the state of the chain overall. Gas usage, event emission, etc. will change, and may cause the state root to differ from the canonical chain. This would cause the executor to fail if we used `transact`. 61 | 62 | ### Chain Reverted 63 | 64 | When a reorg occurs, Reth emits `ExExNotification::ChainReverted`, with the chain of blocks (and their state) that were reverted and are no longer part of canonical mainnet state. `ShadowExEx` handles these notifications by marking the logs as removed in the SQLite database: 65 | 66 | ```rust 67 | ExExNotification::ChainReverted { old: chain } => { 68 | // The chain was reverted to a previous state, so we need to invalidate the 69 | // blocks in the old chain 70 | chain.blocks_iter().for_each(|block| { 71 | let block = block.clone().unseal(); 72 | debug!(block = block.number, "Invalidating shadow logs"); 73 | let sqlite_db = self.sqlite_db.clone(); 74 | 75 | // Create a new runtime to handle the block reorg in the shadow database. 76 | tokio::spawn({ 77 | async move { 78 | let _ = sqlite_db.handle_block_reorg(block.hash_slow()).await; 79 | } 80 | }); 81 | }); 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /crates/exex/src/contracts.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use eyre::{eyre, Result}; 4 | use reth_primitives::revm_primitives::{Address, Bytecode, Bytes, HashMap, B256}; 5 | use serde_json::Value; 6 | 7 | /// A map of addresses to shadow bytecode, which will be used when replaying 8 | /// committed transactions. 9 | #[derive(Clone, Debug)] 10 | pub(crate) struct ShadowContracts { 11 | contracts: HashMap, 12 | code_hashes: HashMap, 13 | } 14 | 15 | impl TryFrom for ShadowContracts { 16 | type Error = eyre::Error; 17 | 18 | fn try_from(value: Value) -> Result { 19 | let contracts = value 20 | .as_object() 21 | .ok_or_else(|| eyre!("`shadow.json` must be an object"))? 22 | .iter() 23 | .map(|(address, bytecode)| { 24 | let address = Address::from_str(address).map_err(|e| { 25 | eyre!("shadow configuration invalid at {address}: invalid address: {e}",) 26 | })?; 27 | let bytecode = Bytecode::new_raw( 28 | Bytes::from_str(bytecode.as_str().ok_or_else(|| { 29 | eyre!( 30 | "shadow configuration invalid at {address}: bytecode must be a string", 31 | ) 32 | })?) 33 | .map_err(|e| { 34 | eyre!("shadow configuration invalid at {address}: invalid bytecode: {e}",) 35 | })?, 36 | ); 37 | Ok((address, bytecode)) 38 | }) 39 | .collect::>>()?; 40 | let code_hashes = 41 | contracts.iter().map(|(address, bytecode)| (*address, bytecode.hash_slow())).collect(); 42 | 43 | Ok(ShadowContracts { contracts, code_hashes }) 44 | } 45 | } 46 | 47 | impl ShadowContracts { 48 | /// Returns the number of shadow contracts. 49 | pub(crate) fn len(&self) -> usize { 50 | self.contracts.len() 51 | } 52 | 53 | /// Returns true if the given address is a shadow contract. 54 | pub(crate) fn is_shadowed(&self, address: &Address) -> bool { 55 | self.contracts.contains_key(address) 56 | } 57 | 58 | /// Returns the shadow bytecode for the given address, if it exists. 59 | pub(crate) fn code(&self, address: &Address) -> Option { 60 | self.contracts.get(address).cloned() 61 | } 62 | 63 | /// Get the code hash for a shadow contract at a given address. 64 | pub(crate) fn code_hash(&self, address: &Address) -> Option { 65 | self.code_hashes.get(address).copied() 66 | } 67 | 68 | /// Retrieves the shadow bytecode associated with a given code hash, 69 | /// if it exists. 70 | pub(crate) fn code_by_hash(&self, code_hash: &B256) -> Option { 71 | self.code_hashes.iter().find_map( 72 | |(address, hash)| { 73 | if hash == code_hash { 74 | self.code(address) 75 | } else { 76 | None 77 | } 78 | }, 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/exex/src/db.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use reth_primitives::{Address, B256, KECCAK_EMPTY, U256}; 4 | use reth_provider::{ProviderError, StateProvider}; 5 | use reth_revm::{ 6 | db::DatabaseRef, 7 | primitives::{AccountInfo, Bytecode}, 8 | Database, 9 | }; 10 | 11 | use crate::contracts::ShadowContracts; 12 | 13 | /// Wrapper around [`StateProviderDatabase`] that implements the revm database trait 14 | /// and also overrides certain methods, such as `basic` and `code_by_hash`, wherever 15 | /// they touch a shadow contract in [`ShadowContracts`] 16 | #[derive(Debug, Clone)] 17 | pub(crate) struct ShadowDatabase { 18 | db: DB, 19 | shadow: ShadowContracts, 20 | } 21 | 22 | impl ShadowDatabase { 23 | /// Create new State with generic StateProvider. 24 | pub(crate) const fn new(db: DB, shadow: ShadowContracts) -> Self { 25 | Self { db, shadow } 26 | } 27 | } 28 | 29 | impl Deref for ShadowDatabase { 30 | type Target = DB; 31 | 32 | fn deref(&self) -> &Self::Target { 33 | &self.db 34 | } 35 | } 36 | 37 | impl DerefMut for ShadowDatabase { 38 | fn deref_mut(&mut self) -> &mut Self::Target { 39 | &mut self.db 40 | } 41 | } 42 | 43 | impl Database for ShadowDatabase { 44 | type Error = ProviderError; 45 | 46 | /// Retrieves basic account information for a given address. 47 | /// 48 | /// Returns `Ok` with `Some(AccountInfo)` if the account exists, 49 | /// `None` if it doesn't, or an error if encountered. 50 | fn basic(&mut self, address: Address) -> Result, Self::Error> { 51 | DatabaseRef::basic_ref(self, address) 52 | } 53 | 54 | /// Retrieves the bytecode associated with a given code hash. 55 | /// 56 | /// Returns `Ok` with the bytecode if found, or the default bytecode otherwise. 57 | fn code_by_hash(&mut self, code_hash: B256) -> Result { 58 | DatabaseRef::code_by_hash_ref(self, code_hash) 59 | } 60 | 61 | /// Retrieves the storage value at a specific index for a given address. 62 | /// 63 | /// Returns `Ok` with the storage value, or the default value if not found. 64 | fn storage(&mut self, address: Address, index: U256) -> Result { 65 | DatabaseRef::storage_ref(self, address, index) 66 | } 67 | 68 | /// Retrieves the block hash for a given block number. 69 | /// 70 | /// Returns `Ok` with the block hash if found, or the default hash otherwise. 71 | /// Note: It safely casts the `number` to `u64`. 72 | fn block_hash(&mut self, number: U256) -> Result { 73 | DatabaseRef::block_hash_ref(self, number) 74 | } 75 | } 76 | 77 | impl DatabaseRef for ShadowDatabase { 78 | type Error = ::Error; 79 | 80 | /// Retrieves basic account information for a given address. 81 | /// 82 | /// Returns `Ok` with `Some(AccountInfo)` if the account exists, 83 | /// `None` if it doesn't, or an error if encountered. 84 | fn basic_ref(&self, address: Address) -> Result, Self::Error> { 85 | Ok(self.db.basic_account(address)?.map(|account| AccountInfo { 86 | balance: account.balance, 87 | nonce: account.nonce, 88 | code_hash: self 89 | .shadow 90 | .code_hash(&address) // Check if the address is a shadow contract, and use that code hash 91 | .unwrap_or_else(|| account.bytecode_hash.unwrap_or(KECCAK_EMPTY)), 92 | code: self.shadow.code(&address), 93 | })) 94 | } 95 | 96 | /// Retrieves the bytecode associated with a given code hash. 97 | /// 98 | /// Returns `Ok` with the bytecode if found, or the default bytecode otherwise. 99 | fn code_by_hash_ref(&self, code_hash: B256) -> Result { 100 | Ok(self.shadow.code_by_hash(&code_hash).unwrap_or_else(|| { 101 | self.bytecode_by_hash(code_hash).ok().flatten().unwrap_or_default().0 102 | })) 103 | } 104 | 105 | /// Retrieves the storage value at a specific index for a given address. 106 | /// 107 | /// Returns `Ok` with the storage value, or the default value if not found. 108 | fn storage_ref(&self, address: Address, index: U256) -> Result { 109 | Ok(self.db.storage(address, B256::new(index.to_be_bytes()))?.unwrap_or_default()) 110 | } 111 | 112 | /// Retrieves the block hash for a given block number. 113 | /// 114 | /// Returns `Ok` with the block hash if found, or the default hash otherwise. 115 | fn block_hash_ref(&self, number: U256) -> Result { 116 | // Attempt to convert U256 to u64 117 | let block_number = match number.try_into() { 118 | Ok(value) => value, 119 | Err(_) => return Err(Self::Error::BlockNumberOverflow(number)), 120 | }; 121 | 122 | // Get the block hash or default hash 123 | Ok(self.db.block_hash(block_number)?.unwrap_or_default()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /crates/exex/src/execution.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use eyre::Result; 4 | use reth_evm_ethereum::EthEvmConfig; 5 | use reth_node_api::{ConfigureEvm, ConfigureEvmEnv}; 6 | use reth_primitives::{ 7 | revm::env::fill_tx_env, Block, BlockWithSenders, ChainSpec, Header, TransactionSigned, 8 | }; 9 | use reth_provider::StateProvider; 10 | use reth_revm::{ 11 | db::{states::bundle_state::BundleRetention, State}, 12 | primitives::{ 13 | CfgEnvWithHandlerCfg, EVMError, ExecutionResult, HashMap, ResultAndState, B256, U256, 14 | }, 15 | DatabaseCommit, Evm, StateBuilder, 16 | }; 17 | use reth_tracing::tracing::{debug, error}; 18 | use shadow_reth_common::{ShadowLog, ToLowerHex}; 19 | 20 | use crate::db::ShadowDatabase; 21 | 22 | /// A block executor which shadows certain contracts, overriding their bytecode. 23 | /// Uses the [`ShadowDatabase`] to shadow the contracts from the provided `shadow.json`. 24 | #[derive(Debug)] 25 | pub(crate) struct ShadowExecutor<'a, DB: StateProvider> { 26 | evm: Evm<'a, (), State>>, 27 | } 28 | 29 | /// Holds the result of a block execution, as well as important 30 | /// information about the block and transactions executed. 31 | #[derive(Debug)] 32 | pub(crate) struct ExecutedBlock { 33 | block: Block, 34 | canonical_block_hash: B256, 35 | results: HashMap, 36 | } 37 | 38 | impl ExecutedBlock { 39 | /// Returns [`ShadowLog`]s from the executed block. 40 | pub(crate) fn logs(&self) -> Vec { 41 | let mut block_log_index = 0; 42 | self.results 43 | .clone() 44 | .into_iter() 45 | .enumerate() 46 | .flat_map(|(transaction_index, (transaction, result))| { 47 | result.into_logs().into_iter().enumerate().map( 48 | move |(transaction_log_index, log)| { 49 | block_log_index += 1; 50 | ShadowLog { 51 | address: log.address.to_lower_hex(), 52 | block_hash: self.canonical_block_hash.to_lower_hex(), 53 | block_log_index, 54 | block_number: self.block.number, 55 | block_timestamp: self.block.timestamp, 56 | transaction_index: transaction_index as u64, 57 | transaction_hash: transaction.hash.to_lower_hex(), 58 | transaction_log_index: transaction_log_index as u64, 59 | removed: false, 60 | data: Some(log.data.data.to_lower_hex()), 61 | topic_0: log.topics().first().map(|t| t.to_lower_hex()), 62 | topic_1: log.topics().get(1).map(|t| t.to_lower_hex()), 63 | topic_2: log.topics().get(2).map(|t| t.to_lower_hex()), 64 | topic_3: log.topics().get(3).map(|t| t.to_lower_hex()), 65 | } 66 | }, 67 | ) 68 | }) 69 | .collect() 70 | } 71 | } 72 | 73 | impl<'a, DB: StateProvider> ShadowExecutor<'a, DB> { 74 | /// Creates a new instance of the ShadowExecutor 75 | pub(crate) fn new( 76 | config: &'a EthEvmConfig, 77 | db: ShadowDatabase, 78 | chain: Arc, 79 | header: &Header, 80 | ) -> Self { 81 | let evm = configure_evm(config, db, chain, header); 82 | Self { evm } 83 | } 84 | 85 | #[allow(clippy::mutable_key_type)] 86 | /// Executes a single block (without verifying them) and returns their [`ExecutionResult`]s 87 | /// within a [`ExecutedBlock`]. 88 | pub(crate) fn execute_one(&mut self, block: BlockWithSenders) -> Result { 89 | // Calculate the canonical block hash, before making state-changing operations. 90 | let canonical_block_hash = block.block.hash_slow(); 91 | 92 | // Extract the transactions from the block. 93 | let transactions = block.clone().into_transactions(); 94 | let mut results = HashMap::with_capacity(transactions.len()); 95 | 96 | if !transactions.is_empty() { 97 | for transaction in transactions { 98 | // Recover the sender of the transaction. 99 | let sender = match transaction.recover_signer() { 100 | Some(sender) => sender, 101 | None => { 102 | debug!(?transaction, "Skipping transaction with invalid signature"); 103 | continue; 104 | } 105 | }; 106 | 107 | // Execute the transaction, do not verify it since we're shadowing certain contracts 108 | // which may not be valid. 109 | fill_tx_env(self.evm.tx_mut(), &transaction, sender); 110 | let ResultAndState { result, state } = match self.evm.transact_preverified() { 111 | Ok(result) => result, 112 | Err(err) => match err { 113 | EVMError::Transaction(err) => { 114 | debug!(%err, ?transaction, "Skipping invalid transaction"); 115 | continue; 116 | } 117 | err => { 118 | error!(%err, ?transaction, "Fatal error during transaction execution"); 119 | continue; 120 | } 121 | }, 122 | }; 123 | 124 | // Commit the state changes to the shadowed database, and store the result of the 125 | // transaction. 126 | self.evm.db_mut().commit(state); 127 | results.insert(transaction, result); 128 | } 129 | 130 | // Merge the transitions into the shadowed database. 131 | self.evm.db_mut().merge_transitions(BundleRetention::Reverts); 132 | } 133 | 134 | Ok(ExecutedBlock { canonical_block_hash, block: block.block, results }) 135 | } 136 | } 137 | 138 | /// Configure EVM with the given database and header. 139 | fn configure_evm<'a, DB: StateProvider>( 140 | config: &'a EthEvmConfig, 141 | db: ShadowDatabase, 142 | chain: Arc, 143 | header: &Header, 144 | ) -> Evm<'a, (), State>> { 145 | let mut evm = config.evm(StateBuilder::new_with_database(db).with_bundle_update().build()); 146 | let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(evm.cfg().clone(), evm.spec_id()); 147 | EthEvmConfig::fill_cfg_and_block_env(&mut cfg, evm.block_mut(), &chain, header, U256::ZERO); 148 | *evm.cfg_mut() = cfg.cfg_env; 149 | 150 | evm 151 | } 152 | -------------------------------------------------------------------------------- /crates/exex/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ShadowExEx is a reth [Execution Extension](https://www.paradigm.xyz/2024/05/reth-exex) which allows for 2 | //! overriding bytecode at specific addresses with custom "shadow" bytecode. 3 | 4 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 5 | 6 | mod contracts; 7 | mod db; 8 | mod execution; 9 | 10 | use std::path::PathBuf; 11 | 12 | use contracts::ShadowContracts; 13 | use execution::ShadowExecutor; 14 | use eyre::{eyre, OptionExt, Result}; 15 | use futures::Future; 16 | use reth_evm_ethereum::EthEvmConfig; 17 | use reth_exex::{ExExContext, ExExEvent, ExExNotification}; 18 | use reth_node_api::FullNodeComponents; 19 | use reth_provider::{DatabaseProviderFactory, HistoricalStateProviderRef}; 20 | use reth_tracing::tracing::{debug, info}; 21 | use serde_json::Value; 22 | use shadow_reth_common::{ShadowSqliteDb, ToLowerHex}; 23 | use tokio::sync::broadcast::Sender; 24 | 25 | use crate::db::ShadowDatabase; 26 | 27 | #[derive(Debug)] 28 | /// The main ExEx struct, which handles loading and parsing shadow configuration, 29 | /// as well as handling ExEx events from reth. 30 | pub struct ShadowExEx { 31 | /// Stores the shadow contracts, a map of addresses to shadow (overridden) bytecode. 32 | contracts: ShadowContracts, 33 | /// The [`ShadowSqliteDb`] for the shadow database. 34 | sqlite_db: ShadowSqliteDb, 35 | 36 | indexed_block_hash_sender: Sender, 37 | } 38 | 39 | impl ShadowExEx { 40 | /// Creates a new instance of the ShadowExEx. This will attempt to load 41 | /// the configuration from `shadow.json` in the current working directory. 42 | pub async fn new(db_path: PathBuf, indexed_block_hash_sender: Sender) -> Result { 43 | // read config from `./shadow.json` as a serde_json::Value 44 | let config: Value = 45 | serde_json::from_str(&std::fs::read_to_string("shadow.json").map_err(|e| { 46 | eyre!("failed to locate `shadow.json` in the current working directory: {}", e) 47 | })?) 48 | .map_err(|e| eyre!("failed to parse `shadow.json`: {}", e))?; 49 | 50 | // parse shadow contracts from the config 51 | let contracts = ShadowContracts::try_from(config)?; 52 | 53 | // get the path to the shadow database 54 | let shadow_db_path = db_path.join("shadow.db"); 55 | debug!("Path to shadow database: {}", shadow_db_path.display()); 56 | 57 | // create a new ShadowSqliteDb for the shadow database 58 | let sqlite_db = ShadowSqliteDb::new( 59 | shadow_db_path.to_str().expect("Failed to convert shadow_db_path to string"), 60 | ) 61 | .await?; 62 | 63 | Ok(Self { contracts, sqlite_db, indexed_block_hash_sender }) 64 | } 65 | 66 | /// The initialization logic of the ExEx is just an async function. 67 | pub async fn init( 68 | ctx: ExExContext, 69 | indexed_block_hash_sender: Sender, 70 | ) -> Result>> { 71 | let db_path = ctx.data_dir.db(); 72 | let this = Self::new(db_path, indexed_block_hash_sender).await?; 73 | 74 | info!("Initialized ShadowExEx with {} shadowed contracts", this.contracts.len()); 75 | 76 | Ok(async move { 77 | this.exex(ctx).await?; 78 | Ok(()) 79 | }) 80 | } 81 | 82 | /// The exex 83 | async fn exex(&self, mut ctx: ExExContext) -> Result<()> { 84 | while let Some(notification) = ctx.notifications.recv().await { 85 | match notification { 86 | ExExNotification::ChainCommitted { new: chain } => { 87 | // Create a read-only database provider that we can use to get historical state 88 | // at the start of the notification chain. i.e. the state at the first block in 89 | // the notification, pre-execution. 90 | let database_provider = ctx.provider().database_provider_ro()?; 91 | let provider = HistoricalStateProviderRef::new( 92 | database_provider.tx_ref(), 93 | chain.first().number, 94 | database_provider.static_file_provider().clone(), 95 | ); 96 | 97 | // Use the database provider to create a [`ShadowDatabase`]. This is a 98 | // [`reth_revm::Database`] implementation that will override the 99 | // bytecode of contracts at specific addresses with custom shadow bytecode, as 100 | // defined in `shadow.json`. 101 | let db = ShadowDatabase::new(provider, self.contracts.clone()); 102 | 103 | let blocks = chain.blocks_iter().collect::>(); 104 | 105 | // Construct a new `ShadowExecutor` with the default config and proper chain 106 | // spec, using the `ShadowDatabase` as the state provider. 107 | let evm_config = EthEvmConfig::default(); 108 | let mut executor = ShadowExecutor::new( 109 | &evm_config, 110 | db, 111 | ctx.config.chain.clone(), 112 | blocks 113 | .first() 114 | .map(|b| b.header()) 115 | .ok_or_eyre("No blocks found in ExEx notification")?, 116 | ); 117 | 118 | // Execute the blocks in the chain, collecting logs from shadowed contracts. 119 | let shadow_logs = blocks 120 | .into_iter() 121 | .map(|block| executor.execute_one(block.clone().unseal())) 122 | .collect::>>()? 123 | .into_iter() 124 | .flat_map(|executed_block| executed_block.logs()) 125 | .filter(|log| { 126 | self.contracts.is_shadowed( 127 | &log.address.parse().expect("failed to parse log address"), 128 | ) 129 | }) 130 | .collect::>(); 131 | 132 | // Create a new task to send the shadow logs to the shadow database. 133 | tokio::spawn({ 134 | let sqlite_db = self.sqlite_db.clone(); 135 | let indexed_block_hash_sender = self.indexed_block_hash_sender.clone(); 136 | async move { 137 | let block_hashes = 138 | shadow_logs.iter().fold(Vec::new(), |mut acc, log| { 139 | match acc.last() { 140 | None => acc.push(log.block_hash.clone()), 141 | Some(last) if last != &log.block_hash => { 142 | acc.push(log.block_hash.clone()) 143 | } 144 | _ => {} 145 | } 146 | 147 | acc 148 | }); 149 | 150 | let _ = sqlite_db.bulk_insert_into_shadow_log_table(shadow_logs).await; 151 | for block_hash in block_hashes { 152 | let _ = indexed_block_hash_sender.send(block_hash); 153 | } 154 | } 155 | }); 156 | 157 | // We're done, so send a FinishedHeight event to the ExEx. 158 | ctx.events.send(ExExEvent::FinishedHeight(chain.tip().number))?; 159 | } 160 | ExExNotification::ChainReverted { old: chain } => { 161 | // The chain was reverted to a previous state, so we need to invalidate the 162 | // blocks in the old chain 163 | chain.blocks_iter().for_each(|block| { 164 | let block = block.clone().unseal(); 165 | debug!(block = block.number, "Invalidating shadow logs"); 166 | 167 | // Create a new task to handle the block reorg in the shadow database. 168 | tokio::spawn({ 169 | let sqlite_db = self.sqlite_db.clone(); 170 | let indexed_block_hash_sender = self.indexed_block_hash_sender.clone(); 171 | async move { 172 | let block_hash = block.hash_slow(); 173 | let _ = sqlite_db.handle_block_reorg(block_hash).await; 174 | let _ = indexed_block_hash_sender.send(block_hash.to_lower_hex()); 175 | } 176 | }); 177 | }); 178 | } 179 | _ => {} 180 | } 181 | } 182 | Ok(()) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /crates/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shadow-reth-rpc" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | keywords.workspace = true 9 | exclude.workspace = true 10 | license.workspace = true 11 | 12 | [lints] 13 | workspace = true 14 | 15 | [dependencies] 16 | # Shadow 17 | shadow-reth-common.workspace = true 18 | 19 | # Reth 20 | reth-node-api.workspace = true 21 | reth-node-builder.workspace = true 22 | reth-primitives.workspace = true 23 | reth-provider.workspace = true 24 | reth-tracing.workspace = true 25 | 26 | # Crates 27 | eyre.workspace = true 28 | tokio.workspace = true 29 | jsonrpsee.workspace = true 30 | serde.workspace = true 31 | sqlx.workspace = true 32 | -------------------------------------------------------------------------------- /crates/rpc/README.md: -------------------------------------------------------------------------------- 1 | # shadow-rpc 2 | 3 | The Shadow RPC extension allows for custom implementations of methods that return information about shadow events. 4 | 5 | ## Overview 6 | 7 | The `ShadowExEx` Reth execution extension generates information about shadowed contracts and persists it in a SQLite database. The `ShadowRpc` RPC extension allows you to define custom methods for retrieving this information. 8 | 9 | ## How does it work? 10 | The RPC is driven by the following type and trait: 11 | 12 | ```rust 13 | use jsonrpsee::{core::RpcResult, proc_macros::rpc}; 14 | use shadow_reth_common::SqliteManager; 15 | 16 | #[derive(Debug)] 17 | pub struct ShadowRpc { 18 | provider: Provider, 19 | sqlite_manager: SqliteManager, 20 | } 21 | 22 | #[rpc(server, namespace = "shadow")] 23 | pub trait ShadowRpcApi { 24 | /// Returns shadow logs. 25 | #[method(name = "getLogs")] 26 | async fn get_logs(&self, params: GetLogsParameters) -> RpcResult>; 27 | } 28 | ``` 29 | 30 | The `#[rpc]` macro generates a `Server` trait prepended with the name of the extension trait and the core logic for the extension trait methods should be implemented as part of this generated trait. For organization, the implementation for each RPC API method are split up into their own modules and included as part of the `apis` module. 31 | 32 | ## Extending the custom namespace 33 | 34 | Currently, the Shadow custom RPC extentsion only implements `shadow_getLogs`, which allows you to retrieve Shadow Events emitted by your shadow contracts. However, you can extend the namespace by doing the following: 35 | 36 | 1. Adjust the `ShadowRpcApi` trait to include the function signature for your desired method along with the desired return type wrapped in an `RpcResult`. You should then decorate the signature with the `#[method(name = ...)]` macro which will add the named method to the RPC namespace. For example, if you wanted to add an equivalent method for `eth_getFilterLogs that returns Shadow Events, you could adjust the trait in the following way: 37 | 38 | ```rust 39 | #[rpc(server, namespace = "shadow")] 40 | pub trait ShadowRpcApi { 41 | /// Returns shadow logs. 42 | #[method(name = "getLogs")] 43 | async fn get_logs(&self, params: GetLogsParameters) -> RpcResult>; 44 | #[method(name = "getFilterLogs")] 45 | fn get_filter_logs(&self, params: ...) -> RpcResult<...> 46 | } 47 | ``` 48 | 49 | 2. Add a new submodule to the `apis` module that contains the core logic for the newly added RPC method. 50 | 51 | ```rust 52 | impl ShadowRpcApiServer for ShadowRpc { 53 | fn get_filter_logs(&self, params: ...) -> RpcResult<...> { ... } 54 | } 55 | ``` 56 | 57 | ### Notes 58 | 59 | Here are some helpful notes when implementing a new method for the custom shadow RPC extension. 60 | 61 | #### Async trait methods 62 | 63 | You may find it necessary to use async functionality in your custom methods. If so, you should mark the function as `async` in the trait definition and then decorate the `impl` block with the `#[async_trait]` macro. You should then be able to freely `await` in your method implementation as you see fit. 64 | 65 | #### Errors 66 | 67 | The `RpcResult` type wraps your intended return type in the following type: `std::result::Result`; this will ensure that RPC method errors are properly propagated and returned to the client. 68 | 69 | #### Providers 70 | 71 | Your method may need to request certain information about the blockchain, e.g. grabbing the corresponding block for a particular hash; a blockchain provider should be used to retrieve that information. To that end, a generic `Provider` type is accessible on the `ShadowRpc` type and can be accessed through `&self.provider` inside your method implementation, provided that you've included `&self` as a parameter in your method signature. Additionally, you may need to extend the implementation of `ShadowRpc` in order to include specific traits that provide the necessary functionality for your new method. Available provider traits can be found in [the documentation for Reth](https://paradigmxyz.github.io/reth/docs/reth/providers/index.html#traits). Be aware that if you are using the provider on a full Reth node to create a `ShadowRpc` instance, a large number of these provider traits are implemented for you already. 72 | -------------------------------------------------------------------------------- /crates/rpc/src/apis/get_logs.rs: -------------------------------------------------------------------------------- 1 | //! Contains logic for a shadow RPC equivalent of `eth_getLogs`. 2 | 3 | use super::{AddressRepresentation, RpcLog}; 4 | use jsonrpsee::core::RpcResult; 5 | use reth_provider::{BlockNumReader, BlockReaderIdExt}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::{ 9 | shadow_logs_query::{exec_query, ValidatedQueryParams}, 10 | ShadowRpc, 11 | }; 12 | /// Unvalidated parameters for `shadow_getLogs` RPC requests. 13 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct GetLogsParameters { 16 | /// Contains contract addresses from which logs should originate. 17 | pub address: Option, 18 | /// Hash of block from which logs should originate. Using this field is equivalent 19 | /// to passing identical values for `fromBlock` and `toBlock`. 20 | pub block_hash: Option, 21 | /// Start of block range from which logs should originate. 22 | pub from_block: Option, 23 | /// End of block range from which logs should originate. 24 | pub to_block: Option, 25 | /// Array of 32-byte data topics. 26 | pub topics: Option>, 27 | } 28 | 29 | pub(crate) async fn get_logs

( 30 | rpc: &ShadowRpc

, 31 | params: GetLogsParameters, 32 | ) -> RpcResult> 33 | where 34 | P: BlockNumReader + BlockReaderIdExt + Clone + Unpin + 'static, 35 | { 36 | let validated_param_objs = 37 | ValidatedQueryParams::from_get_logs_parameters(&rpc.provider, params)?; 38 | 39 | let mut results: Vec = vec![]; 40 | for query_params in [validated_param_objs] { 41 | let intermediate_results = exec_query(query_params, &rpc.sqlite_manager.pool).await?; 42 | let mut result = 43 | intermediate_results.into_iter().map(RpcLog::from).collect::>(); 44 | results.append(&mut result); 45 | } 46 | 47 | Ok(results) 48 | } 49 | -------------------------------------------------------------------------------- /crates/rpc/src/apis/mod.rs: -------------------------------------------------------------------------------- 1 | mod get_logs; 2 | mod subscribe; 3 | mod types; 4 | 5 | pub(crate) use get_logs::*; 6 | pub(crate) use subscribe::*; 7 | pub(crate) use types::*; 8 | 9 | use crate::{ShadowRpc, ShadowRpcApiServer}; 10 | use jsonrpsee::{ 11 | core::{async_trait, RpcResult, SubscriptionResult}, 12 | PendingSubscriptionSink, 13 | }; 14 | use reth_provider::{BlockNumReader, BlockReaderIdExt}; 15 | 16 | #[async_trait] 17 | impl

ShadowRpcApiServer for ShadowRpc

18 | where 19 | P: BlockNumReader + BlockReaderIdExt + Clone + Unpin + 'static, 20 | { 21 | async fn get_logs(&self, params: GetLogsParameters) -> RpcResult> { 22 | get_logs(self, params).await 23 | } 24 | 25 | async fn subscribe( 26 | &self, 27 | pending: PendingSubscriptionSink, 28 | params: SubscribeParameters, 29 | ) -> SubscriptionResult { 30 | subscribe(self, pending, params).await 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/rpc/src/apis/subscribe.rs: -------------------------------------------------------------------------------- 1 | //! Contains logic for a shadow RPC equivalent of `eth_subscribe` of `type` `logs`. 2 | 3 | use super::AddressRepresentation; 4 | use crate::{ 5 | apis::RpcLog, 6 | shadow_logs_query::{exec_query, ValidatedQueryParams}, 7 | ShadowRpc, 8 | }; 9 | use jsonrpsee::{ 10 | core::SubscriptionResult, 11 | types::{error::INTERNAL_ERROR_CODE, ErrorObject}, 12 | PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink, 13 | }; 14 | use reth_provider::{BlockNumReader, BlockReaderIdExt}; 15 | use reth_tracing::tracing::warn; 16 | use serde::{Deserialize, Serialize}; 17 | use shadow_reth_common::ShadowSqliteDb; 18 | use tokio::sync::broadcast::{error::RecvError, Receiver}; 19 | 20 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 21 | pub struct SubscribeParameters { 22 | pub address: Option, 23 | pub topics: Option>, 24 | } 25 | 26 | pub(crate) async fn subscribe

( 27 | rpc: &ShadowRpc

, 28 | pending: PendingSubscriptionSink, 29 | params: SubscribeParameters, 30 | ) -> SubscriptionResult 31 | where 32 | P: BlockNumReader + BlockReaderIdExt + Clone + Unpin + 'static, 33 | { 34 | let sink = pending.accept().await?; 35 | tokio::spawn({ 36 | let provider = rpc.provider.clone(); 37 | let sqlite_manager = rpc.sqlite_manager.clone(); 38 | let indexed_block_hash_receiver = rpc.indexed_block_hash_receiver.resubscribe(); 39 | async move { 40 | let _ = handle_accepted( 41 | provider, 42 | sqlite_manager, 43 | indexed_block_hash_receiver, 44 | sink, 45 | params, 46 | ) 47 | .await; 48 | } 49 | }); 50 | 51 | Ok(()) 52 | } 53 | 54 | async fn handle_accepted( 55 | provider: impl BlockNumReader + BlockReaderIdExt + Clone + Unpin + 'static, 56 | sqlite_manager: ShadowSqliteDb, 57 | mut indexed_block_hash_receiver: Receiver, 58 | accepted_sink: SubscriptionSink, 59 | params: SubscribeParameters, 60 | ) -> Result<(), ErrorObject<'static>> { 61 | loop { 62 | match indexed_block_hash_receiver.recv().await { 63 | Ok(block_hash) => { 64 | let query_params = ValidatedQueryParams::from_subscribe_parameters( 65 | &provider, 66 | params.clone(), 67 | block_hash, 68 | )?; 69 | let intermediate_results = exec_query(query_params, &sqlite_manager.pool).await?; 70 | for result in intermediate_results.into_iter().map(RpcLog::from) { 71 | let message = SubscriptionMessage::from_json(&result).map_err(|e| { 72 | ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, e.to_string(), None) 73 | })?; 74 | 75 | accepted_sink.send(message).await.map_err(|e| { 76 | ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, e.to_string(), None) 77 | })?; 78 | } 79 | } 80 | Err(RecvError::Lagged(lag_count)) => { 81 | warn!("lagged by {} messages; consider increasing buffer if syncing", lag_count); 82 | } 83 | Err(RecvError::Closed) => { 84 | // The ExEx has exited, so we should exit as well. 85 | break; 86 | } 87 | } 88 | } 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /crates/rpc/src/apis/types.rs: -------------------------------------------------------------------------------- 1 | use reth_primitives::hex; 2 | use serde::{Deserialize, Serialize}; 3 | use shadow_reth_common::ShadowLog; 4 | 5 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 6 | #[serde(untagged)] 7 | pub enum AddressRepresentation { 8 | ArrayOfStrings(Vec), 9 | Bytes([u8; 20]), 10 | String(String), 11 | } 12 | 13 | /// Inner result type for `shadow_getLogs` and `shadow_subscribe` RPC responses. 14 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 15 | #[serde(rename_all = "camelCase")] 16 | pub struct RpcLog { 17 | /// Contract address from which the log originated. 18 | pub address: String, 19 | /// Hash of block from which the log originated. 20 | pub block_hash: String, 21 | /// Block number from which the log originated. 22 | pub block_number: String, 23 | /// Contains one or more 32-byte non-indexed arguments of the log. 24 | pub data: Option, 25 | /// Integer of the log index in the containing block. 26 | pub log_index: String, 27 | /// Indicates whether the log has been removed from the canonical chain. 28 | pub removed: bool, 29 | /// Array of topics. 30 | pub topics: [Option; 4], 31 | /// Hash of transaction from which the log originated. 32 | pub transaction_hash: String, 33 | /// Integer of the transaction index position from which the log originated. 34 | pub transaction_index: String, 35 | } 36 | 37 | impl From for RpcLog { 38 | fn from(value: ShadowLog) -> Self { 39 | Self { 40 | address: value.address, 41 | block_hash: value.block_hash, 42 | block_number: hex::encode(value.block_number.to_be_bytes()), 43 | data: value.data, 44 | log_index: value.block_log_index.to_string(), 45 | removed: value.removed, 46 | topics: [value.topic_0, value.topic_1, value.topic_2, value.topic_3], 47 | transaction_hash: value.transaction_hash, 48 | transaction_index: value.transaction_index.to_string(), 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ShadowRPC is a reth RPC extension which allows for reading 2 | //! shadow data written to SQLite by [`reth-shadow-exex`] 3 | 4 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 5 | 6 | /// Contains logic for custom RPC API methods. 7 | pub(crate) mod apis; 8 | pub(crate) mod shadow_logs_query; 9 | 10 | use std::path::PathBuf; 11 | 12 | use apis::{GetLogsParameters, RpcLog, SubscribeParameters}; 13 | use eyre::{eyre, Result}; 14 | use jsonrpsee::{ 15 | core::{RpcResult, SubscriptionResult}, 16 | proc_macros::rpc, 17 | }; 18 | use reth_node_api::FullNodeComponents; 19 | use reth_node_builder::rpc::RpcContext; 20 | use reth_provider::{BlockNumReader, BlockReaderIdExt}; 21 | use shadow_reth_common::ShadowSqliteDb; 22 | use tokio::sync::broadcast::Receiver; 23 | 24 | #[rpc(server, namespace = "shadow")] 25 | pub trait ShadowRpcApi { 26 | /// Returns shadow logs. 27 | #[method(name = "getLogs")] 28 | async fn get_logs(&self, params: GetLogsParameters) -> RpcResult>; 29 | 30 | /// Create a shadow logs subscription. 31 | #[subscription(name = "subscribe" => "subscription", unsubscribe = "unsubscribe", item = RpcLog)] 32 | async fn subscribe(&self, params: SubscribeParameters) -> SubscriptionResult; 33 | } 34 | 35 | /// Wrapper around an RPC provider and a database connection pool. 36 | #[derive(Debug)] 37 | pub struct ShadowRpc

{ 38 | provider: P, 39 | /// Database manager. 40 | sqlite_manager: ShadowSqliteDb, 41 | /// Receives block hashes as they are indexed by the exex. 42 | indexed_block_hash_receiver: Receiver, 43 | } 44 | 45 | impl ShadowRpc { 46 | /// Instatiate a Shadow RPC API, building a connection pool to the SQLite database 47 | /// and initializing tables. 48 | pub async fn new( 49 | provider: Provider, 50 | db_path: &str, 51 | indexed_block_hash_receiver: Receiver, 52 | ) -> Result> { 53 | Ok(Self { 54 | provider, 55 | sqlite_manager: ShadowSqliteDb::new(db_path).await?, 56 | indexed_block_hash_receiver, 57 | }) 58 | } 59 | 60 | /// Initializes ShadowRpc, to be called from the `.extend_rpc_modules` reth hook 61 | /// on node startup. 62 | pub fn init( 63 | ctx: RpcContext<'_, Node>, 64 | db_path_obj: PathBuf, 65 | indexed_block_hash_receiver: Receiver, 66 | ) -> Result<()> 67 | where 68 | Node: FullNodeComponents, 69 | Node::Provider: BlockNumReader + BlockReaderIdExt + Clone + Unpin + 'static, 70 | { 71 | // Clone the provider so we can move it into the RPC builder thread 72 | let provider = ctx.provider().clone(); 73 | 74 | // Start a new thread, build the ShadowRpc, and join it. 75 | // 76 | // We have to do it this way to avoid spawning a runtime within a runtime. 77 | let shadow_rpc = std::thread::spawn(move || { 78 | let rt = tokio::runtime::Runtime::new().expect("failed to spawn blocking runtime"); 79 | rt.block_on(ShadowRpc::new( 80 | provider, 81 | db_path_obj.to_str().ok_or_else(|| eyre!("failed to parse DB path"))?, 82 | indexed_block_hash_receiver, 83 | )) 84 | }) 85 | .join() 86 | .map_err(|_| eyre!("failed to join ShadowRpc thread"))??; 87 | 88 | // Merge the ShadowRpc into the reth context, which will make the API available. 89 | ctx.modules 90 | .merge_configured(shadow_rpc.into_rpc()) 91 | .map_err(|e| eyre!("failed to extend w/ ShadowRpc: {e}"))?; 92 | 93 | Ok(()) 94 | } 95 | } 96 | 97 | #[cfg(test)] 98 | mod tests { 99 | use jsonrpsee::rpc_params; 100 | use reth_primitives::{hex, Block, Header}; 101 | use reth_provider::test_utils::MockEthProvider; 102 | use shadow_reth_common::{ShadowLog, ToLowerHex}; 103 | 104 | use crate::{ 105 | apis::{AddressRepresentation, GetLogsParameters, RpcLog, SubscribeParameters}, 106 | ShadowRpc, ShadowRpcApiServer, 107 | }; 108 | 109 | #[tokio::test] 110 | async fn test_shadow_subscribe() { 111 | let mock_provider = MockEthProvider::default(); 112 | 113 | let first_block = Block { 114 | header: Header { number: 18870000, ..Default::default() }, 115 | ..Default::default() 116 | }; 117 | let first_block_hash = first_block.hash_slow(); 118 | 119 | let last_block = Block { 120 | header: Header { number: 18870001, ..Default::default() }, 121 | ..Default::default() 122 | }; 123 | let last_block_hash = last_block.hash_slow(); 124 | 125 | mock_provider.extend_blocks([ 126 | (first_block_hash, first_block.clone()), 127 | (last_block_hash, last_block.clone()), 128 | ]); 129 | 130 | let (tx, rx) = tokio::sync::broadcast::channel(1); 131 | 132 | let rpc = ShadowRpc::new(mock_provider, ":memory:", rx).await.unwrap(); 133 | 134 | let logs = vec![ 135 | ShadowLog { 136 | address: "0x0fbc0a9be1e87391ed2c7d2bb275bec02f53241f".to_string(), 137 | block_hash: "0x4131d538cf705c267da7f448ec7460b177f40d28115ad290ba6a1fd734afe280" 138 | .to_string(), 139 | block_log_index: 0, 140 | block_number: 18870000, 141 | block_timestamp: 1703595263, 142 | transaction_index: 167, 143 | transaction_hash: "0x8bf2361656e0ea6f338ad17ac3cd616f8eea9bb17e1afa1580802e9d3231c203" 144 | .to_string(), 145 | transaction_log_index: 26, 146 | removed: false, 147 | data: Some("0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000049dc9ce34ad2a2177480000000000000000000000000000000000000000000000000432f754f7158ad80000000000000000000000000000000000000000000000000000000000000000".to_string()), 148 | topic_0: Some("0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822".to_string()), 149 | topic_1: Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), 150 | topic_2: Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), 151 | topic_3: None, 152 | }, 153 | ShadowLog { 154 | address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2".to_string(), 155 | block_hash: last_block_hash.to_string(), 156 | block_log_index: 0, 157 | block_number: 18870001, 158 | block_timestamp: 1703595275, 159 | transaction_index: 2, 160 | transaction_hash: "0xd02dc650cc9a34def3d7a78808a36a8cb2e292613c2989f4313155e8e4af9b0f".to_string(), 161 | transaction_log_index: 0, 162 | removed: false, 163 | data: Some("0x0000000000000000000000000000000000000000000000001bc16d674ec80000".to_string()), 164 | topic_0: Some("0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c".to_string()), 165 | topic_1: Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), 166 | topic_2: None, 167 | topic_3: None, 168 | }, 169 | ]; 170 | 171 | // Keep a clone of the log we expect to receive via the subscription for assert 172 | let expected_log = RpcLog::from(logs[1].clone()); 173 | rpc.sqlite_manager.bulk_insert_into_shadow_log_table(logs).await.unwrap(); 174 | 175 | let params = SubscribeParameters { 176 | address: Some(AddressRepresentation::ArrayOfStrings(vec![ 177 | "0x0fbc0a9be1e87391ed2c7d2bb275bec02f53241f".to_string(), 178 | "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2".to_string(), 179 | "0xc55126051b22ebb829d00368f4b12bde432de5da".to_string(), 180 | ])), 181 | topics: None, 182 | }; 183 | 184 | // Create a subscription on `shadow_subscribe` 185 | let mut sub = rpc 186 | .into_rpc() 187 | .subscribe_unbounded("shadow_subscribe", rpc_params!(params)) 188 | .await 189 | .unwrap(); 190 | 191 | // Send the last block hash to the rpc receiver to mock the exex indexing the block 192 | tx.send(last_block_hash.to_lower_hex()).expect("failed to send block hash"); 193 | 194 | // Receive the RPC log from the subscription 195 | let (result, _id) = sub.next::().await.unwrap().unwrap(); 196 | 197 | assert_eq!(result, expected_log); 198 | } 199 | 200 | #[tokio::test] 201 | async fn test_shadow_get_logs() { 202 | let mock_provider = MockEthProvider::default(); 203 | 204 | let first_block = Block { 205 | header: Header { number: 18870000, ..Default::default() }, 206 | ..Default::default() 207 | }; 208 | let first_block_hash = first_block.hash_slow(); 209 | 210 | let last_block = Block { 211 | header: Header { number: 18870001, ..Default::default() }, 212 | ..Default::default() 213 | }; 214 | let last_block_hash = last_block.hash_slow(); 215 | 216 | mock_provider.extend_blocks([ 217 | (first_block_hash, first_block.clone()), 218 | (last_block_hash, last_block.clone()), 219 | ]); 220 | 221 | let (_, rx) = tokio::sync::broadcast::channel(1); 222 | 223 | let rpc = ShadowRpc::new(mock_provider, ":memory:", rx).await.unwrap(); 224 | 225 | let logs = vec![ 226 | ShadowLog { 227 | address: "0x0fbc0a9be1e87391ed2c7d2bb275bec02f53241f".to_string(), 228 | block_hash: "0x4131d538cf705c267da7f448ec7460b177f40d28115ad290ba6a1fd734afe280" 229 | .to_string(), 230 | block_log_index: 0, 231 | block_number: 18870000, 232 | block_timestamp: 1703595263, 233 | transaction_index: 167, 234 | transaction_hash: "0x8bf2361656e0ea6f338ad17ac3cd616f8eea9bb17e1afa1580802e9d3231c203" 235 | .to_string(), 236 | transaction_log_index: 26, 237 | removed: false, 238 | data: Some("0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000049dc9ce34ad2a2177480000000000000000000000000000000000000000000000000432f754f7158ad80000000000000000000000000000000000000000000000000000000000000000".to_string()), 239 | topic_0: Some("0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822".to_string()), 240 | topic_1: Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), 241 | topic_2: Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), 242 | topic_3: None, 243 | }, 244 | ShadowLog { 245 | address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2".to_string(), 246 | block_hash: "0x3cac643a6a1af584681a6a6dc632cd110a479c9c642e2da92b73fefb45739165".to_string(), 247 | block_log_index: 0, 248 | block_number: 18870001, 249 | block_timestamp: 1703595275, 250 | transaction_index: 2, 251 | transaction_hash: "0xd02dc650cc9a34def3d7a78808a36a8cb2e292613c2989f4313155e8e4af9b0f".to_string(), 252 | transaction_log_index: 0, 253 | removed: false, 254 | data: Some("0x0000000000000000000000000000000000000000000000001bc16d674ec80000".to_string()), 255 | topic_0: Some("0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c".to_string()), 256 | topic_1: Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), 257 | topic_2: None, 258 | topic_3: None, 259 | }, 260 | ]; 261 | 262 | rpc.sqlite_manager.bulk_insert_into_shadow_log_table(logs).await.unwrap(); 263 | 264 | let params = GetLogsParameters { 265 | address: Some(AddressRepresentation::ArrayOfStrings(vec![ 266 | "0x0fbc0a9be1e87391ed2c7d2bb275bec02f53241f".to_string(), 267 | "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2".to_string(), 268 | "0xc55126051b22ebb829d00368f4b12bde432de5da".to_string(), 269 | ])), 270 | block_hash: None, 271 | from_block: Some("0x11feef0".to_string()), 272 | to_block: Some("0x11feef1".to_string()), 273 | topics: None, 274 | }; 275 | 276 | let resp = rpc.get_logs(params).await.unwrap(); 277 | 278 | let expected = vec![ 279 | RpcLog { 280 | address: "0x0fbc0a9be1e87391ed2c7d2bb275bec02f53241f".to_string(), 281 | block_hash: "0x4131d538cf705c267da7f448ec7460b177f40d28115ad290ba6a1fd734afe280".to_string(), 282 | block_number: hex::encode(18870000u64.to_be_bytes()), 283 | data: Some("0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000049dc9ce34ad2a2177480000000000000000000000000000000000000000000000000432f754f7158ad80000000000000000000000000000000000000000000000000000000000000000".to_string()), 284 | log_index: 0u64.to_string(), 285 | removed: false, 286 | topics: [Some("0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822".to_string()), Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), None], 287 | transaction_hash: "0x8bf2361656e0ea6f338ad17ac3cd616f8eea9bb17e1afa1580802e9d3231c203".to_string(), 288 | transaction_index: 167u64.to_string() 289 | }, 290 | RpcLog { 291 | address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2".to_string(), 292 | block_hash: "0x3cac643a6a1af584681a6a6dc632cd110a479c9c642e2da92b73fefb45739165".to_string(), 293 | block_number: hex::encode(18870001u64.to_be_bytes()), 294 | data: Some("0x0000000000000000000000000000000000000000000000001bc16d674ec80000".to_string()), 295 | log_index: 0u64.to_string(), 296 | removed: false, 297 | topics: [Some("0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c".to_string()), Some("0x0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad".to_string()), None, None], 298 | transaction_hash: "0xd02dc650cc9a34def3d7a78808a36a8cb2e292613c2989f4313155e8e4af9b0f".to_string(), 299 | transaction_index: 2u64.to_string() 300 | } 301 | ]; 302 | 303 | assert_eq!(resp, expected); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /crates/rpc/src/shadow_logs_query.rs: -------------------------------------------------------------------------------- 1 | use std::{num::ParseIntError, str::FromStr}; 2 | 3 | use jsonrpsee::{ 4 | core::RpcResult, 5 | types::{error::INTERNAL_ERROR_CODE, ErrorObject}, 6 | }; 7 | use reth_primitives::{hex, Address, BlockNumberOrTag, B256}; 8 | use reth_provider::{BlockNumReader, BlockReaderIdExt}; 9 | use shadow_reth_common::ShadowLog; 10 | use sqlx::{Pool, Sqlite}; 11 | 12 | use crate::apis::{AddressRepresentation, GetLogsParameters, SubscribeParameters}; 13 | 14 | pub(crate) async fn exec_query( 15 | query_params: ValidatedQueryParams, 16 | pool: &Pool, 17 | ) -> RpcResult> { 18 | let base_stmt = " 19 | SELECT 20 | address, 21 | block_hash, 22 | block_log_index, 23 | block_number, 24 | block_timestamp, 25 | data, 26 | removed, 27 | topic_0, 28 | topic_1, 29 | topic_2, 30 | topic_3, 31 | transaction_hash, 32 | transaction_index, 33 | transaction_log_index 34 | FROM shadow_logs"; 35 | let sql = format!("{base_stmt} {query_params}"); 36 | let raw_rows: Vec = sqlx::query_as(&sql) 37 | .fetch_all(pool) 38 | .await 39 | .map_err(|e| ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, e.to_string(), None))?; 40 | 41 | raw_rows 42 | .into_iter() 43 | .map(ShadowLog::try_from) 44 | .collect::, ParseIntError>>() 45 | .map_err(|e| ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, e.to_string(), None)) 46 | } 47 | 48 | /// Helper type for ease of use in converting rows from the `shadow_getLogs` 49 | /// query into the `GetLogsResult` type which is used in `GetLogsResponse`. 50 | #[derive(Debug, sqlx::FromRow)] 51 | pub(crate) struct RawGetLogsRow { 52 | /// Address from which a log originated. 53 | pub(crate) address: Vec, 54 | /// Hash of bock from which a log orignated. 55 | pub(crate) block_hash: Vec, 56 | /// Integer of the log index position in its containing block. 57 | pub(crate) block_log_index: String, 58 | /// Block number from which a log originated. 59 | pub(crate) block_number: String, 60 | /// Timestamp of block from which the log originated. 61 | pub(crate) block_timestamp: String, 62 | /// Contains one or more 32-byte non-indexed arguments of the log. 63 | pub(crate) data: Option>, 64 | /// Indicates whether a log was removed from the canonical chain. 65 | pub(crate) removed: bool, 66 | /// Hash of event signature. 67 | pub(crate) topic_0: Option>, 68 | /// Additional topic #1. 69 | pub(crate) topic_1: Option>, 70 | /// Additional topic #2. 71 | pub(crate) topic_2: Option>, 72 | /// Additional topic #3. 73 | pub(crate) topic_3: Option>, 74 | /// Hash of the transaction from which a log originated. 75 | pub(crate) transaction_hash: Vec, 76 | /// Integer of the transaction index position in a log's containing block. 77 | pub(crate) transaction_index: String, 78 | /// Integer of the log index position within a transaction. 79 | pub(crate) transaction_log_index: String, 80 | } 81 | 82 | impl TryFrom for ShadowLog { 83 | type Error = ParseIntError; 84 | 85 | fn try_from(value: RawGetLogsRow) -> Result { 86 | Ok(Self { 87 | address: format!("0x{}", hex::encode(value.address)), 88 | block_hash: format!("0x{}", hex::encode(value.block_hash)), 89 | block_log_index: u64::from_str(&value.block_log_index)?, 90 | block_number: u64::from_str(&value.block_number)?, 91 | block_timestamp: u64::from_str(&value.block_timestamp)?, 92 | transaction_index: u64::from_str(&value.transaction_index)?, 93 | transaction_hash: format!("0x{}", hex::encode(value.transaction_hash)), 94 | transaction_log_index: u64::from_str(&value.transaction_log_index)?, 95 | removed: value.removed, 96 | data: value.data.map(|d| format!("0x{}", hex::encode(d))), 97 | topic_0: value.topic_0.map(|t| format!("0x{}", hex::encode(t))), 98 | topic_1: value.topic_1.map(|t| format!("0x{}", hex::encode(t))), 99 | topic_2: value.topic_2.map(|t| format!("0x{}", hex::encode(t))), 100 | topic_3: value.topic_3.map(|t| format!("0x{}", hex::encode(t))), 101 | }) 102 | } 103 | } 104 | 105 | #[derive(Debug, Clone, PartialEq, Eq)] 106 | pub(crate) enum ValidatedBlockIdParam { 107 | /// Block hash from which logs will be filtered. 108 | BlockHash(String), 109 | /// Start and end of block range from which logs will be filtered. 110 | BlockRange(u64, u64), 111 | } 112 | 113 | /// Validated query parameter object. Instances are considered to be well-formed 114 | /// and are used in query construction and execution. 115 | #[derive(Debug, Clone, PartialEq, Eq)] 116 | pub(crate) struct ValidatedQueryParams { 117 | pub(crate) block_id: ValidatedBlockIdParam, 118 | /// Set of addresses from which logs will be filtered. 119 | pub(crate) addresses: Vec, 120 | /// Set of log topics. 121 | pub(crate) topics: [Option; 4], 122 | } 123 | 124 | impl ValidatedQueryParams { 125 | fn validate_addresses(address: Option) -> RpcResult> { 126 | let v = if let Some(addr_repr) = address { 127 | match addr_repr { 128 | AddressRepresentation::String(addr) => { 129 | let parsed = addr 130 | .parse::

() 131 | .map_err(|e| { 132 | ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, e.to_string(), None) 133 | })? 134 | .to_string(); 135 | vec![parsed] 136 | } 137 | AddressRepresentation::ArrayOfStrings(array) => array 138 | .into_iter() 139 | .map(|addr| { 140 | addr.parse::
().map_err(|e| { 141 | ErrorObject::owned::<()>(INTERNAL_ERROR_CODE, e.to_string(), None) 142 | }) 143 | }) 144 | .collect::>>()? 145 | .into_iter() 146 | .map(|a| a.to_string()) 147 | .collect::>(), 148 | AddressRepresentation::Bytes(bytes) => { 149 | vec![Address::from_slice(&bytes).to_string()] 150 | } 151 | } 152 | } else { 153 | vec![] 154 | }; 155 | 156 | Ok(v) 157 | } 158 | 159 | fn validate_topics(topics: Option>) -> RpcResult<[Option; 4]> { 160 | let v = if let Some(t_list) = topics { 161 | if t_list.len() > 4 { 162 | return Err(ErrorObject::owned::<()>( 163 | 32002, 164 | "Only up to four topics are allowed", 165 | None, 166 | )); 167 | } else { 168 | let mut topics: [Option; 4] = [None, None, None, None]; 169 | 170 | for (idx, topic) in t_list.into_iter().enumerate() { 171 | topics[idx] = Some(topic); 172 | } 173 | 174 | topics 175 | } 176 | } else { 177 | [None, None, None, None] 178 | }; 179 | 180 | Ok(v) 181 | } 182 | 183 | fn validate_block_id( 184 | provider: &(impl BlockNumReader + BlockReaderIdExt), 185 | block_hash: Option, 186 | from_block: Option, 187 | to_block: Option, 188 | resolve_block_hash: bool, 189 | ) -> RpcResult { 190 | let v = match (block_hash, from_block, to_block) { 191 | (None, None, None) => { 192 | let num = match provider.block_by_number_or_tag(BlockNumberOrTag::Latest) { 193 | Ok(Some(b)) => b.number, 194 | Ok(None) => { 195 | return Err(ErrorObject::owned::<()>( 196 | -1, 197 | "No block found for block number or tag: latest", 198 | None, 199 | )) 200 | } 201 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 202 | }; 203 | ValidatedBlockIdParam::BlockRange(num, num) 204 | } 205 | (None, None, Some(to_block)) => { 206 | let from = match provider.block_by_number_or_tag(BlockNumberOrTag::Latest) { 207 | Ok(Some(b)) => b.number, 208 | Ok(None) => { 209 | return Err(ErrorObject::owned::<()>( 210 | -1, 211 | "No block found for block number or tag: latest", 212 | None, 213 | )) 214 | } 215 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 216 | }; 217 | let to_tag = BlockNumberOrTag::from_str(&to_block) 218 | .map_err(|e| ErrorObject::owned::<()>(-1, e.to_string(), None))?; 219 | let to = if let BlockNumberOrTag::Number(n) = to_tag { 220 | n 221 | } else { 222 | match provider.block_by_number_or_tag(to_tag) { 223 | Ok(Some(b)) => b.number, 224 | Ok(None) => { 225 | return Err(ErrorObject::owned::<()>( 226 | -1, 227 | format!("No block found for block number or tag: {to_tag}"), 228 | None, 229 | )) 230 | } 231 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 232 | } 233 | }; 234 | ValidatedBlockIdParam::BlockRange(from, to) 235 | } 236 | (None, Some(from_block), None) => { 237 | let from_tag = BlockNumberOrTag::from_str(&from_block) 238 | .map_err(|e| ErrorObject::owned::<()>(-1, e.to_string(), None))?; 239 | let from = if let BlockNumberOrTag::Number(n) = from_tag { 240 | n 241 | } else { 242 | match provider.block_by_number_or_tag(from_tag) { 243 | Ok(Some(b)) => b.number, 244 | Ok(None) => { 245 | return Err(ErrorObject::owned::<()>( 246 | -1, 247 | format!("No block found for block number or tag: {from_tag}"), 248 | None, 249 | )) 250 | } 251 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 252 | } 253 | }; 254 | let to = match provider.block_by_number_or_tag(BlockNumberOrTag::Latest) { 255 | Ok(Some(b)) => b.number, 256 | Ok(None) => { 257 | return Err(ErrorObject::owned::<()>( 258 | -1, 259 | "No block found for block number or tag: latest", 260 | None, 261 | )) 262 | } 263 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 264 | }; 265 | ValidatedBlockIdParam::BlockRange(from, to) 266 | } 267 | (None, Some(from_block), Some(to_block)) => { 268 | let from_tag = BlockNumberOrTag::from_str(&from_block) 269 | .map_err(|e| ErrorObject::owned::<()>(-1, e.to_string(), None))?; 270 | let from = if let BlockNumberOrTag::Number(n) = from_tag { 271 | n 272 | } else { 273 | match provider.block_by_number_or_tag(from_tag) { 274 | Ok(Some(b)) => b.number, 275 | Ok(None) => { 276 | return Err(ErrorObject::owned::<()>( 277 | -1, 278 | format!("No block found for block number or tag: {from_tag}"), 279 | None, 280 | )) 281 | } 282 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 283 | } 284 | }; 285 | let to_tag = BlockNumberOrTag::from_str(&to_block) 286 | .map_err(|e| ErrorObject::owned::<()>(-1, e.to_string(), None))?; 287 | let to = if let BlockNumberOrTag::Number(n) = to_tag { 288 | n 289 | } else { 290 | match provider.block_by_number_or_tag(to_tag) { 291 | Ok(Some(b)) => b.number, 292 | Ok(None) => { 293 | return Err(ErrorObject::owned::<()>( 294 | -1, 295 | format!("No block found for block number or tag: {to_tag}"), 296 | None, 297 | )) 298 | } 299 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 300 | } 301 | }; 302 | 303 | ValidatedBlockIdParam::BlockRange(from, to) 304 | } 305 | (Some(block_hash), None, None) if resolve_block_hash => { 306 | let num = match provider.block_by_hash( 307 | B256::from_str(&block_hash) 308 | .map_err(|e| ErrorObject::owned::<()>(-1, e.to_string(), None))?, 309 | ) { 310 | Ok(Some(b)) => b.number, 311 | Ok(None) => { 312 | return Err(ErrorObject::owned::<()>( 313 | -1, 314 | format!("No block found for block hash: {block_hash}"), 315 | None, 316 | )) 317 | } 318 | Err(e) => return Err(ErrorObject::owned::<()>(-1, e.to_string(), None)), 319 | }; 320 | ValidatedBlockIdParam::BlockRange(num, num) 321 | } 322 | (Some(block_hash), None, None) => ValidatedBlockIdParam::BlockHash(block_hash), 323 | (Some(_), Some(_), _) | (Some(_), _, Some(_)) => return Err(ErrorObject::owned::<()>( 324 | -32001, 325 | "Parameters fromBlock and toBlock cannot be used if blockHash parameter is present", 326 | None, 327 | )), 328 | }; 329 | 330 | Ok(v) 331 | } 332 | 333 | pub(crate) fn from_get_logs_parameters( 334 | provider: &(impl BlockNumReader + BlockReaderIdExt), 335 | params: GetLogsParameters, 336 | ) -> RpcResult { 337 | let addresses = Self::validate_addresses(params.address)?; 338 | let block_id = Self::validate_block_id( 339 | provider, 340 | params.block_hash, 341 | params.from_block, 342 | params.to_block, 343 | true, 344 | )?; 345 | let topics = Self::validate_topics(params.topics)?; 346 | 347 | Ok(ValidatedQueryParams { block_id, addresses, topics }) 348 | } 349 | 350 | pub(crate) fn from_subscribe_parameters( 351 | provider: &(impl BlockNumReader + BlockReaderIdExt), 352 | params: SubscribeParameters, 353 | block_hash: String, 354 | ) -> RpcResult { 355 | let addresses = Self::validate_addresses(params.address)?; 356 | let topics = Self::validate_topics(params.topics)?; 357 | let block_id = Self::validate_block_id(provider, Some(block_hash), None, None, false)?; 358 | 359 | Ok(ValidatedQueryParams { block_id, addresses, topics }) 360 | } 361 | } 362 | 363 | impl std::fmt::Display for ValidatedQueryParams { 364 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 365 | let address_clause = if !self.addresses.is_empty() { 366 | Some(format!( 367 | "address IN ({})", 368 | self.addresses 369 | .iter() 370 | .map(|addr| format!("X'{}'", &addr[2..])) 371 | .collect::>() 372 | .join(", ") 373 | )) 374 | } else { 375 | None 376 | }; 377 | 378 | let block_range_clause = match &self.block_id { 379 | ValidatedBlockIdParam::BlockHash(block_hash) => { 380 | Some(format!("block_hash = X'{}'", &block_hash[2..])) 381 | } 382 | ValidatedBlockIdParam::BlockRange(from_block, to_block) => { 383 | Some(format!("block_number BETWEEN {} AND {}", from_block, to_block)) 384 | } 385 | }; 386 | 387 | let topic_0_clause = self.topics[0].as_ref().map(|t| format!("topic_0 = X'{}'", &t[2..])); 388 | 389 | let topic_1_clause = self.topics[1].as_ref().map(|t| format!("topic_1 = X'{}'", &t[2..])); 390 | 391 | let topic_2_clause = self.topics[2].as_ref().map(|t| format!("topic_2 = X'{}'", &t[2..])); 392 | 393 | let topic_3_clause = self.topics[3].as_ref().map(|t| format!("topic_3 = X'{}'", &t[2..])); 394 | 395 | let clauses = [ 396 | address_clause, 397 | block_range_clause, 398 | topic_0_clause, 399 | topic_1_clause, 400 | topic_2_clause, 401 | topic_3_clause, 402 | ]; 403 | 404 | let filtered_clauses = clauses.into_iter().flatten().collect::>(); 405 | 406 | if !filtered_clauses.is_empty() { 407 | write!(f, "WHERE {}", filtered_clauses.join(" AND ")) 408 | } else { 409 | write!(f, "") 410 | } 411 | } 412 | } 413 | 414 | #[cfg(test)] 415 | mod tests { 416 | use reth_primitives::{Address, Block, BlockHash, Header}; 417 | use reth_provider::test_utils::MockEthProvider; 418 | 419 | use super::{ValidatedBlockIdParam, ValidatedQueryParams}; 420 | use crate::apis::{AddressRepresentation, GetLogsParameters, SubscribeParameters}; 421 | 422 | #[test] 423 | fn test_display() { 424 | let mock_provider = MockEthProvider::default(); 425 | 426 | let first_block = 427 | Block { header: Header { number: 0, ..Default::default() }, ..Default::default() }; 428 | let first_block_hash = first_block.hash_slow(); 429 | 430 | let last_block = 431 | Block { header: Header { number: 10, ..Default::default() }, ..Default::default() }; 432 | let last_block_hash = last_block.hash_slow(); 433 | 434 | mock_provider 435 | .extend_blocks([(first_block_hash, first_block), (last_block_hash, last_block)]); 436 | 437 | let subscribe_params = SubscribeParameters { 438 | address: Some(AddressRepresentation::ArrayOfStrings(vec![Address::ZERO.to_string()])), 439 | topics: Some(vec!["0xfoo".to_string()]), 440 | }; 441 | 442 | assert_eq!( 443 | format!( 444 | "{}", 445 | ValidatedQueryParams::from_subscribe_parameters( 446 | &mock_provider, 447 | subscribe_params, 448 | BlockHash::ZERO.to_string(), 449 | ) 450 | .unwrap() 451 | ), 452 | "WHERE address IN (X'0000000000000000000000000000000000000000') AND block_hash = X'0000000000000000000000000000000000000000000000000000000000000000' AND topic_0 = X'foo'" 453 | ); 454 | 455 | let get_logs_params = GetLogsParameters { 456 | address: Some(AddressRepresentation::ArrayOfStrings(vec![Address::ZERO.to_string()])), 457 | block_hash: Some(last_block_hash.to_string()), 458 | from_block: None, 459 | to_block: None, 460 | topics: Some(vec!["0xfoo".to_string()]), 461 | }; 462 | 463 | assert_eq!( 464 | format!( 465 | "{}", 466 | ValidatedQueryParams::from_get_logs_parameters(&mock_provider, get_logs_params,) 467 | .unwrap() 468 | ), 469 | "WHERE address IN (X'0000000000000000000000000000000000000000') AND block_number BETWEEN 10 AND 10 AND topic_0 = X'foo'" 470 | ); 471 | } 472 | 473 | #[test] 474 | fn test_from_subscribe_parameters() { 475 | let mock_provider = MockEthProvider::default(); 476 | 477 | let first_block = 478 | Block { header: Header { number: 0, ..Default::default() }, ..Default::default() }; 479 | let first_block_hash = first_block.hash_slow(); 480 | 481 | let last_block = 482 | Block { header: Header { number: 10, ..Default::default() }, ..Default::default() }; 483 | let last_block_hash = last_block.hash_slow(); 484 | 485 | mock_provider 486 | .extend_blocks([(first_block_hash, first_block), (last_block_hash, last_block)]); 487 | 488 | let params = SubscribeParameters { 489 | address: Some(AddressRepresentation::ArrayOfStrings(vec![Address::ZERO.to_string()])), 490 | topics: None, 491 | }; 492 | 493 | assert_eq!( 494 | ValidatedQueryParams::from_subscribe_parameters( 495 | &mock_provider, 496 | params, 497 | BlockHash::ZERO.to_string(), 498 | ) 499 | .unwrap(), 500 | ValidatedQueryParams { 501 | addresses: vec![Address::ZERO.to_string()], 502 | block_id: ValidatedBlockIdParam::BlockHash(BlockHash::ZERO.to_string()), 503 | topics: [None, None, None, None] 504 | } 505 | ) 506 | } 507 | 508 | #[test] 509 | fn test_from_get_logs_parameters() { 510 | let mock_provider = MockEthProvider::default(); 511 | 512 | let first_block = 513 | Block { header: Header { number: 0, ..Default::default() }, ..Default::default() }; 514 | let first_block_hash = first_block.hash_slow(); 515 | 516 | let last_block = 517 | Block { header: Header { number: 10, ..Default::default() }, ..Default::default() }; 518 | let last_block_hash = last_block.hash_slow(); 519 | 520 | mock_provider 521 | .extend_blocks([(first_block_hash, first_block), (last_block_hash, last_block)]); 522 | 523 | let params_with_block_hash = GetLogsParameters { 524 | address: Some(AddressRepresentation::ArrayOfStrings(vec![ 525 | "0x0000000000000000000000000000000000000000".to_string(), 526 | ])), 527 | block_hash: Some(last_block_hash.to_string()), 528 | from_block: None, 529 | to_block: None, 530 | topics: None, 531 | }; 532 | 533 | assert!(ValidatedQueryParams::from_get_logs_parameters( 534 | &mock_provider, 535 | params_with_block_hash 536 | ) 537 | .is_ok()); 538 | 539 | let params_with_defaults = GetLogsParameters { 540 | address: Some(AddressRepresentation::ArrayOfStrings(vec![ 541 | "0x0000000000000000000000000000000000000000".to_string(), 542 | ])), 543 | block_hash: None, 544 | from_block: None, 545 | to_block: None, 546 | topics: None, 547 | }; 548 | 549 | let validated = 550 | ValidatedQueryParams::from_get_logs_parameters(&mock_provider, params_with_defaults); 551 | 552 | assert_eq!( 553 | validated.unwrap(), 554 | ValidatedQueryParams { 555 | addresses: vec!["0x0000000000000000000000000000000000000000".to_string()], 556 | block_id: ValidatedBlockIdParam::BlockRange(10, 10), 557 | topics: [None, None, None, None] 558 | } 559 | ); 560 | 561 | let params_with_block_tags = GetLogsParameters { 562 | address: Some(AddressRepresentation::ArrayOfStrings(vec![ 563 | "0x0000000000000000000000000000000000000000".to_string(), 564 | ])), 565 | block_hash: None, 566 | from_block: Some("earliest".to_string()), 567 | to_block: Some("latest".to_string()), 568 | topics: None, 569 | }; 570 | let validated = 571 | ValidatedQueryParams::from_get_logs_parameters(&mock_provider, params_with_block_tags); 572 | 573 | assert_eq!( 574 | validated.unwrap(), 575 | ValidatedQueryParams { 576 | addresses: vec!["0x0000000000000000000000000000000000000000".to_string()], 577 | block_id: ValidatedBlockIdParam::BlockRange(0, 10), 578 | topics: [None, None, None, None] 579 | } 580 | ); 581 | 582 | let params_with_non_array_address = GetLogsParameters { 583 | address: Some(AddressRepresentation::String( 584 | "0x0000000000000000000000000000000000000000".to_string(), 585 | )), 586 | block_hash: None, 587 | from_block: Some("earliest".to_string()), 588 | to_block: Some("latest".to_string()), 589 | topics: None, 590 | }; 591 | let validated = ValidatedQueryParams::from_get_logs_parameters( 592 | &mock_provider, 593 | params_with_non_array_address, 594 | ); 595 | 596 | assert_eq!( 597 | validated.unwrap(), 598 | ValidatedQueryParams { 599 | addresses: vec!["0x0000000000000000000000000000000000000000".to_string()], 600 | block_id: ValidatedBlockIdParam::BlockRange(0, 10), 601 | topics: [None, None, None, None] 602 | } 603 | ); 604 | 605 | let params_with_bytes_as_address = GetLogsParameters { 606 | address: Some(AddressRepresentation::Bytes([ 607 | 192, 42, 170, 57, 178, 35, 254, 141, 10, 14, 92, 79, 39, 234, 217, 8, 60, 117, 108, 608 | 194, 609 | ])), 610 | block_hash: None, 611 | from_block: Some("earliest".to_string()), 612 | to_block: Some("latest".to_string()), 613 | topics: None, 614 | }; 615 | let validated = ValidatedQueryParams::from_get_logs_parameters( 616 | &mock_provider, 617 | params_with_bytes_as_address, 618 | ); 619 | 620 | assert_eq!( 621 | validated.unwrap(), 622 | ValidatedQueryParams { 623 | addresses: vec!["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string()], 624 | block_id: ValidatedBlockIdParam::BlockRange(0, 10), 625 | topics: [None, None, None, None] 626 | } 627 | ); 628 | 629 | let params_with_invalid_address = GetLogsParameters { 630 | address: Some(AddressRepresentation::ArrayOfStrings(vec![ 631 | "0x0000000000000000000000000000000000000000".to_string(), 632 | ])), 633 | block_hash: Some(first_block_hash.to_string()), 634 | from_block: Some(first_block_hash.to_string()), 635 | to_block: Some(last_block_hash.to_string()), 636 | topics: None, 637 | }; 638 | assert!(ValidatedQueryParams::from_get_logs_parameters( 639 | &mock_provider, 640 | params_with_invalid_address 641 | ) 642 | .is_err()); 643 | 644 | let params_with_block_hash_and_range = GetLogsParameters { 645 | address: Some(AddressRepresentation::ArrayOfStrings(vec![ 646 | "0x0000000000000000000000000000000000000000".to_string(), 647 | ])), 648 | block_hash: Some(first_block_hash.to_string()), 649 | from_block: Some(first_block_hash.to_string()), 650 | to_block: Some(last_block_hash.to_string()), 651 | topics: None, 652 | }; 653 | assert!(ValidatedQueryParams::from_get_logs_parameters( 654 | &mock_provider, 655 | params_with_block_hash_and_range 656 | ) 657 | .is_err()); 658 | } 659 | } 660 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | imports_granularity = "Crate" 3 | use_small_heuristics = "Max" 4 | comment_width = 100 5 | wrap_comments = true 6 | binop_separator = "Back" 7 | trailing_comma = "Vertical" 8 | trailing_semicolon = false 9 | use_field_init_shorthand = true 10 | format_code_in_doc_comments = true 11 | doc_comment_code_block_width = 100 12 | -------------------------------------------------------------------------------- /shadow.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "0x6060604052600436106100AF576000357C0100000000000000000000000000000000000000000000000000000000900463FFFFFFFF16806306FDDE03146100B9578063095EA7B31461014757806318160DDD146101A157806323B872DD146101CA5780632E1A7D4D14610243578063313CE5671461026657806370A082311461029557806395D89B41146102E2578063A9059CBB14610370578063D0E30DB0146103CA578063DD62ED3E146103D4575B6100B7610440565B005B34156100C457600080FD5B6100CC6104DD565B6040518080602001828103825283818151815260200191508051906020019080838360005B8381101561010C5780820151818401526020810190506100F1565B50505050905090810190601F1680156101395780820380516001836020036101000A031916815260200191505B509250505060405180910390F35B341561015257600080FD5B610187600480803573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1690602001909190803590602001909190505061057B565B604051808215151515815260200191505060405180910390F35B34156101AC57600080FD5B6101B461066D565B6040518082815260200191505060405180910390F35B34156101D557600080FD5B610229600480803573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1690602001909190803573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1690602001909190803590602001909190505061068C565B604051808215151515815260200191505060405180910390F35B341561024E57600080FD5B6102646004808035906020019091905050610A3E565B005B341561027157600080FD5B610279610B6A565B604051808260FF1660FF16815260200191505060405180910390F35B34156102A057600080FD5B6102CC600480803573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16906020019091905050610B7D565B6040518082815260200191505060405180910390F35B34156102ED57600080FD5B6102F5610B95565B6040518080602001828103825283818151815260200191508051906020019080838360005B8381101561033557808201518184015260208101905061031A565B50505050905090810190601F1680156103625780820380516001836020036101000A031916815260200191505B509250505060405180910390F35B341561037B57600080FD5B6103B0600480803573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16906020019091908035906020019091905050610C33565B604051808215151515815260200191505060405180910390F35B6103D2610440565B005B34156103DF57600080FD5B61042A600480803573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1690602001909190803573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16906020019091905050610C48565B6040518082815260200191505060405180910390F35B34600360003373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168152602001908152602001600020600082825401925050819055503373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF167FE1FFFCC4923D04B559F4D29A8BFC6CDA04EB5B0D3C460751C2402C5C5CC9109C346040518082815260200191505060405180910390A2565B60008054600181600116156101000203166002900480601F0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601F1061054857610100808354040283529160200191610573565B820191906000526020600020905B81548152906001019060200180831161055657829003601F168201915B505050505081565B600081600460003373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200190815260200160002060008573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168152602001908152602001600020819055508273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF163373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF167F8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925846040518082815260200191505060405180910390A36001905092915050565B60003073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1631905090565B600081600360008673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200190815260200160002054101515156106DC57600080FD5B3373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16141580156107B457507FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF600460008673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200190815260200160002060003373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1681526020019081526020016000205414155B156108CF5781600460008673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200190815260200160002060003373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168152602001908152602001600020541015151561084457600080FD5B81600460008673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200190815260200160002060003373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168152602001908152602001600020600082825403925050819055505B81600360008673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1681526020019081526020016000206000828254039250508190555081600360008573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168152602001908152602001600020600082825401925050819055508273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF167FDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF846040518082815260200191505060405180910390A38273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF167FE7742D659C2C3C18FBA9C357096ED6D568223CB89064E8BC947B709CBA2A6AB7846040518082815260200191505060405180910390A3600190509392505050565B80600360003373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1681526020019081526020016000205410151515610A8C57600080FD5B80600360003373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168152602001908152602001600020600082825403925050819055503373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166108FC829081150290604051600060405180830381858888F193505050501515610B1957600080FD5B3373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF167F7FCF532C15F0A6DB0BD6D0E038BEA71D30D808C7D98CB3BF7268A95BF5081B65826040518082815260200191505060405180910390A250565B600260009054906101000A900460FF1681565B60036020528060005260406000206000915090505481565B60018054600181600116156101000203166002900480601F016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610C2B5780601F10610C0057610100808354040283529160200191610C2B565B820191906000526020600020905B815481529060010190602001808311610C0E57829003601F168201915B505050505081565B6000610C4033848461068C565B905092915050565B60046020528160005260406000206020528060005260406000206000915091505054815600A165627A7A7230582023EEE356D563DAF6796AD35279DEB666F23A7C169F45B25A0A0886DDF71E6D940029", 3 | "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": "0x60806040526004361061018F5760003560E01C80638803DBEE116100D6578063C45A01551161007F578063E8E3370011610059578063E8E3370014610488578063F305D719146104B7578063FB3BDB41146104CA576101D5565B8063C45A015514610433578063D06CA61F14610448578063DED9382A14610468576101D5565B8063AF2979EB116100B0578063AF2979EB146103E0578063B6F9DE9514610400578063BAA2ABDE14610413576101D5565B80638803DBEE1461037E578063AD5C46481461039E578063AD615DEC146103C0576101D5565B80634A25D94A11610138578063791AC94711610112578063791AC9471461032B5780637FF36AB51461034B57806385F8C2591461035E576101D5565B80634A25D94A146102CB5780635B0D5984146102EB5780635C11D7951461030B576101D5565B80631F00CA74116101695780631F00CA741461026B5780632195995C1461028B57806338ED1739146102AB576101D5565B806302751CEC146101DA578063054D50D41461021157806318CBAFE51461023E576101D5565B366101D5573373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC216146101D357FE5B005B600080FD5B3480156101E657600080FD5B506101FA6101F5366004616C4F565B6104DD565B604051610208929190617C2E565B60405180910390F35B34801561021D57600080FD5B5061023161022C366004617027565B61061D565B6040516102089190617C25565B34801561024A57600080FD5B5061025E610259366004616FB6565B610634565B6040516102089190617535565B34801561027757600080FD5B5061025E610286366004616EDC565B610DB1565B34801561029757600080FD5B506101FA6102A6366004616B27565B610DE7565B3480156102B757600080FD5B5061025E6102C6366004616FB6565B610F04565B3480156102D757600080FD5B5061025E6102E6366004616FB6565B611089565B3480156102F757600080FD5B50610231610306366004616CAC565B61123B565B34801561031757600080FD5B506101D3610326366004616FB6565B61136C565B34801561033757600080FD5B506101D3610346366004616FB6565B611A87565B61025E610359366004616E77565B61236F565B34801561036A57600080FD5B50610231610379366004617027565B612AE6565B34801561038A57600080FD5B5061025E610399366004616FB6565B612AF3565B3480156103AA57600080FD5B506103B3612BDE565B6040516102089190617448565B3480156103CC57600080FD5B506102316103DB366004617027565B612C02565B3480156103EC57600080FD5B506102316103FB366004616C4F565B612C0F565B6101D361040E366004616E77565B612DE7565B34801561041F57600080FD5B506101FA61042E366004616AB6565B613749565B34801561043F57600080FD5B506103B36139C7565B34801561045457600080FD5B5061025E610463366004616EDC565B6139EB565B34801561047457600080FD5B506101FA610483366004616CAC565B613A18565B34801561049457600080FD5B506104A86104A3366004616BD4565B613B4F565B60405161020893929190617C77565B6104A86104C5366004616C4F565B613C9F565B61025E6104D8366004616E77565B613F82565B6000808242811015610524576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B60405180910390FD5B610553897F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC28A8A8A308A613749565B9093509150610563898685614732565B6040517F2E1A7D4D00000000000000000000000000000000000000000000000000000000815273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC21690632E1A7D4D906105D5908590600401617C25565B600060405180830381600087803B1580156105EF57600080FD5B505AF1158015610603573D6000803E3D6000FD5B50505050610611858361485C565B50965096945050505050565B600061062A848484614915565B90505B9392505050565B60608142811015610671576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC21686867FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81018181106106D657FE5B90506020020160208101906106EB9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614610738576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177A3565B6107967F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F898888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506149FB92505050565B915086826001845103815181106107A957FE5B602002602001015110156107E9576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906179E2565B6107F1616889565B61082F878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250339250614B2C915050565B90506108DE8787600081811061084157FE5B90506020020160208101906108569190616A7E565B336108C47F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8B8B600081811061088857FE5B905060200201602081019061089D9190616A7E565B8C8C60018181106108AA57FE5B90506020020160208101906108BF9190616A7E565B614C3A565B866000815181106108D157FE5B6020026020010151614CC8565B61091D83888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250614DF5915050565B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16632E1A7D4D8460018651038151811061096957FE5B60200260200101516040518263FFFFFFFF1660E01B815260040161098D9190617C25565B600060405180830381600087803B1580156109A757600080FD5B505AF11580156109BB573D6000803E3D6000FD5B505050506109E085846001865103815181106109D357FE5B602002602001015161485C565B80600001516020015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B158015610A2E57600080FD5B505AFA158015610A42573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190610A669190616E0B565B5082516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A0909101526020808201510151604080517F0902F1AC000000000000000000000000000000000000000000000000000000008152905173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90921691630902F1AC91600480820192606092909190829003018186803B158015610AF857600080FD5B505AFA158015610B0C573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190610B309190616E0B565B5060208301516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A0909101528686600081610B6057FE5B9050602002016020810190610B759190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B8152600401610BAD9190617448565B60206040518083038186803B158015610BC557600080FD5B505AFA158015610BD9573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190610BFD9190616E5F565B60408201516020015286867FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8101818110610C3457FE5B9050602002016020810190610C499190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B8152600401610C819190617448565B60206040518083038186803B158015610C9957600080FD5B505AFA158015610CAD573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190610CD19190616E5F565B816040015160600181815250507F7C35DBE99F3782224B7A94577650863AB74E9569C2A4E35EEF7B7D11FD29FA2A8160A001513385600081518110610D1257FE5B6020026020010151846060015187600189510381518110610D2F57FE5B60200260200101518660800151610D7E8E8E80806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250505060208B01518D9150614FDC565B8860C001518960400151604051610D9D99989796959493929190617B30565B60405180910390A150509695505050505050565B6060610DDE7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8484615222565B90505B92915050565B6000806000610E177F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8F8F614C3A565B9050600087610E26578C610E48565B7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B6040517FD505ACCF00000000000000000000000000000000000000000000000000000000815290915073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83169063D505ACCF90610EA9903390309086908F908E908E908E90600401617469565B600060405180830381600087803B158015610EC357600080FD5B505AF1158015610ED7573D6000803E3D6000FD5B50505050610EEA8F8F8F8F8F8F8F613749565B809450819550505050509B509B9950505050505050505050565B60608142811015610F41576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B610F9F7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F898888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506149FB92505050565B91508682600184510381518110610FB257FE5B60200260200101511015610FF2576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906179E2565B610FFA616889565B611038878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250339250614B2C915050565B905061104A8787600081811061084157FE5B6109E0838888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508A9250614DF5915050565B606081428110156110C6576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC21686867FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF810181811061112B57FE5B90506020020160208101906111409190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF161461118D576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177A3565B6111EB7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061522292505050565B915086826000815181106111FB57FE5B602002602001015111156107E9576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906178CB565B6000806112897F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8D7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2614C3A565B9050600086611298578B6112BA565B7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B6040517FD505ACCF00000000000000000000000000000000000000000000000000000000815290915073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83169063D505ACCF9061131B903390309086908E908D908D908D90600401617469565B600060405180830381600087803B15801561133557600080FD5B505AF1158015611349573D6000803E3D6000FD5B5050505061135B8D8D8D8D8D8D612C0F565B9D9C50505050505050505050505050565B80428110156113A7576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B611426858560008181106113B757FE5B90506020020160208101906113CC9190616A7E565B336114207F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F898960008181106113FE57FE5B90506020020160208101906114139190616A7E565B8A8A60018181106108AA57FE5B8A614CC8565B61142E616889565B61146C868680806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250339250614B2C915050565B90506114766168EF565B604080518082019091528088887FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81018181106114AF57FE5B90506020020160208101906114C49190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231886040518263FFFFFFFF1660E01B81526004016114FC9190617448565B60206040518083038186803B15801561151457600080FD5B505AFA158015611528573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061154C9190616E5F565B81526020016000815250905061159687878080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525089925061537B915050565B86867FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81018181106115C457FE5B90506020020160208101906115D99190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231866040518263FFFFFFFF1660E01B81526004016116119190617448565B60206040518083038186803B15801561162957600080FD5B505AFA15801561163D573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906116619190616E5F565B602082018190528151899161167B9163FFFFFFFF61567316565B10156116B3576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906179E2565B81600001516020015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B15801561170157600080FD5B505AFA158015611715573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906117399190616E0B565B5083516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A0909101526020808301510151604080517F0902F1AC000000000000000000000000000000000000000000000000000000008152905173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90921691630902F1AC91600480820192606092909190829003018186803B1580156117CB57600080FD5B505AFA1580156117DF573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906118039190616E0B565B5060208401516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A090910152868660008161183357FE5B90506020020160208101906118489190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016118809190617448565B60206040518083038186803B15801561189857600080FD5B505AFA1580156118AC573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906118D09190616E5F565B60408301516020015286867FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF810181811061190757FE5B905060200201602081019061191C9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016119549190617448565B60206040518083038186803B15801561196C57600080FD5B505AFA158015611980573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906119A49190616E5F565B826040015160600181815250507F7C35DBE99F3782224B7A94577650863AB74E9569C2A4E35EEF7B7D11FD29FA2A8260A00151338B85606001516119F98660000151876020015161567390919063FFFFFFFF16565B8760800151611A558E8E80806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250508B5160208D0151611A4B9350915063FFFFFFFF61567316565B8B602001516156B0565B8960C001518A60400151604051611A7499989796959493929190617B30565B60405180910390A1505050505050505050565B8042811015611AC2576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC21685857FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8101818110611B2757FE5B9050602002016020810190611B3C9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614611B89576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177A3565B611B91616889565B611BCF868680806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250339250614B2C915050565B9050611C5086866000818110611BE157FE5B9050602002016020810190611BF69190616A7E565B33611C4A7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8A8A6000818110611C2857FE5B9050602002016020810190611C3D9190616A7E565B8B8B60018181106108AA57FE5B8B614CC8565B611C586168EF565B604051806040016040528088886000818110611C7057FE5B9050602002016020810190611C859190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231306040518263FFFFFFFF1660E01B8152600401611CBD9190617448565B60206040518083038186803B158015611CD557600080FD5B505AFA158015611CE9573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190611D0D9190616E5F565B815260200160008152509050611D5787878080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525030925061537B915050565B86866000818110611D6457FE5B9050602002016020810190611D799190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231306040518263FFFFFFFF1660E01B8152600401611DB19190617448565B60206040518083038186803B158015611DC957600080FD5B505AFA158015611DDD573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190611E019190616E5F565B60208201526040517F70A0823100000000000000000000000000000000000000000000000000000000815260009073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC216906370A0823190611E7B903090600401617448565B60206040518083038186803B158015611E9357600080FD5B505AFA158015611EA7573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190611ECB9190616E5F565B905088811015611F07576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906179E2565B6040517F2E1A7D4D00000000000000000000000000000000000000000000000000000000815273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC21690632E1A7D4D90611F79908490600401617C25565B600060405180830381600087803B158015611F9357600080FD5B505AF1158015611FA7573D6000803E3D6000FD5B50505050611FB5868261485C565B82600001516020015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B15801561200357600080FD5B505AFA158015612017573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061203B9190616E0B565B5084516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A0909101526020808401510151604080517F0902F1AC000000000000000000000000000000000000000000000000000000008152905173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90921691630902F1AC91600480820192606092909190829003018186803B1580156120CD57600080FD5B505AFA1580156120E1573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906121059190616E0B565B5060208501516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A090910152878760008161213557FE5B905060200201602081019061214A9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016121829190617448565B60206040518083038186803B15801561219A57600080FD5B505AFA1580156121AE573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906121D29190616E5F565B60408401516020015287877FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF810181811061220957FE5B905060200201602081019061221E9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016122569190617448565B60206040518083038186803B15801561226E57600080FD5B505AFA158015612282573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906122A69190616E5F565B60408401516060015260A0830151825160208401517F7C35DBE99F3782224B7A94577650863AB74E9569C2A4E35EEF7B7D11FD29FA2A929133916122EF9163FFFFFFFF61567316565B866060015185886080015161233C8F8F80806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250505060208D01518B91506156B0565B8A60C001518B6040015160405161235B99989796959493929190617B30565B60405180910390A150505050505050505050565B606081428110156123AC576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16868660008181106123F057FE5B90506020020160208101906124059190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614612452576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177A3565B6124B07F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F348888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506149FB92505050565B915086826001845103815181106124C357FE5B60200260200101511015612503576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906179E2565B61250B616889565B612549878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250339250614B2C915050565B90507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663D0E30DB08460008151811061259457FE5B60200260200101516040518263FFFFFFFF1660E01B81526004016000604051808303818588803B1580156125C757600080FD5B505AF11580156125DB573D6000803E3D6000FD5B50505050507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663A9059CBB61264D7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8A8A6000818110611C2857FE5B8560008151811061265A57FE5B60200260200101516040518363FFFFFFFF1660E01B815260040161267F92919061750F565B602060405180830381600087803B15801561269957600080FD5B505AF11580156126AD573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906126D19190616D45565B6126D757FE5B612716838888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508A9250614DF5915050565B80600001516020015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B15801561276457600080FD5B505AFA158015612778573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061279C9190616E0B565B5082516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A0909101526020808201510151604080517F0902F1AC000000000000000000000000000000000000000000000000000000008152905173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90921691630902F1AC91600480820192606092909190829003018186803B15801561282E57600080FD5B505AFA158015612842573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906128669190616E0B565B5060208301516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A090910152868660008161289657FE5B90506020020160208101906128AB9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016128E39190617448565B60206040518083038186803B1580156128FB57600080FD5B505AFA15801561290F573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906129339190616E5F565B60408201516020015286867FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF810181811061296A57FE5B905060200201602081019061297F9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016129B79190617448565B60206040518083038186803B1580156129CF57600080FD5B505AFA1580156129E3573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190612A079190616E5F565B816040015160600181815250507F7C35DBE99F3782224B7A94577650863AB74E9569C2A4E35EEF7B7D11FD29FA2A8160A001513385600081518110612A4857FE5B6020026020010151846060015187600189510381518110612A6557FE5B60200260200101518660800151612AB48E8E80806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250505060208B01518D9150614FDC565B8860C001518960400151604051612AD399989796959493929190617B30565B60405180910390A1505095945050505050565B600061062A8484846158B0565B60608142811015612B30576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B612B8E7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061522292505050565B91508682600081518110612B9E57FE5B60200260200101511115610FF2576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906178CB565B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC281565B600061062A848484615996565B60008142811015612C4C576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B612C7B887F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC28989893089613749565B6040517F70A08231000000000000000000000000000000000000000000000000000000008152909350612D2E91508990869073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8316906370A0823190612CD9903090600401617448565B60206040518083038186803B158015612CF157600080FD5B505AFA158015612D05573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190612D299190616E5F565B614732565B6040517F2E1A7D4D00000000000000000000000000000000000000000000000000000000815273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC21690632E1A7D4D90612DA0908590600401617C25565B600060405180830381600087803B158015612DBA57600080FD5B505AF1158015612DCE573D6000803E3D6000FD5B50505050612DDC848361485C565B509695505050505050565B8042811015612E22576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1685856000818110612E6657FE5B9050602002016020810190612E7B9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614612EC8576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177A3565B612ED0616889565B612F0E868680806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250339250614B2C915050565B905060003490507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663D0E30DB0346040518263FFFFFFFF1660E01B81526004016000604051808303818588803B158015612F7D57600080FD5B505AF1158015612F91573D6000803E3D6000FD5B50505050507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663A9059CBB6130037F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8A8A6000818110611C2857FE5B346040518363FFFFFFFF1660E01B815260040161302192919061750F565B602060405180830381600087803B15801561303B57600080FD5B505AF115801561304F573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906130739190616D45565B61307957FE5B6130816168EF565B604080518082019091528089897FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81018181106130BA57FE5B90506020020160208101906130CF9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231896040518263FFFFFFFF1660E01B81526004016131079190617448565B60206040518083038186803B15801561311F57600080FD5B505AFA158015613133573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906131579190616E5F565B8152602001600081525090506131A18888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508A925061537B915050565B87877FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81018181106131CF57FE5B90506020020160208101906131E49190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231876040518263FFFFFFFF1660E01B815260040161321C9190617448565B60206040518083038186803B15801561323457600080FD5B505AFA158015613248573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061326C9190616E5F565B602082015280518990613350908A8A7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81018181106132A757FE5B90506020020160208101906132BC9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A082318A6040518263FFFFFFFF1660E01B81526004016132F49190617448565B60206040518083038186803B15801561330C57600080FD5B505AFA158015613320573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906133449190616E5F565B9063FFFFFFFF61567316565B1015613388576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906179E2565B82600001516020015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B1580156133D657600080FD5B505AFA1580156133EA573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061340E9190616E0B565B5084516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A0909101526020808401510151604080517F0902F1AC000000000000000000000000000000000000000000000000000000008152905173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90921691630902F1AC91600480820192606092909190829003018186803B1580156134A057600080FD5B505AFA1580156134B4573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906134D89190616E0B565B5060208501516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A090910152878760008161350857FE5B905060200201602081019061351D9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016135559190617448565B60206040518083038186803B15801561356D57600080FD5B505AFA158015613581573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906135A59190616E5F565B60408401516020015287877FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81018181106135DC57FE5B90506020020160208101906135F19190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016136299190617448565B60206040518083038186803B15801561364157600080FD5B505AFA158015613655573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906136799190616E5F565B836040015160600181815250507F7C35DBE99F3782224B7A94577650863AB74E9569C2A4E35EEF7B7D11FD29FA2A8360A00151338486606001516136CE8660000151876020015161567390919063FFFFFFFF16565B886080015161372A8F8F80806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250508B5160208D01516137209350915063FFFFFFFF61567316565B8C602001516156B0565B8A60C001518B60400151604051611A7499989796959493929190617B30565B6000808242811015613787576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B60006137B47F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8C8C614C3A565B6040517F23B872DD00000000000000000000000000000000000000000000000000000000815290915073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8216906323B872DD9061380D90339085908E906004016174B7565B602060405180830381600087803B15801561382757600080FD5B505AF115801561383B573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061385F9190616D45565B506000808273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166389AFCB44896040518263FFFFFFFF1660E01B815260040161389C9190617448565B6040805180830381600087803B1580156138B557600080FD5B505AF11580156138C9573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906138ED9190616F93565B9150915060006138FD8E8E615A38565B5090508073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168E73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF161461393A57818361393D565B82825B90975095508A87101561397C576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617928565B898610156139B6576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617746565B505050505097509795505050505050565B7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F81565B6060610DDE7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F84846149FB565B6000806000613A687F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8E7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2614C3A565B9050600087613A77578C613A99565B7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B6040517FD505ACCF00000000000000000000000000000000000000000000000000000000815290915073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83169063D505ACCF90613AFA903390309086908F908E908E908E90600401617469565B600060405180830381600087803B158015613B1457600080FD5B505AF1158015613B28573D6000803E3D6000FD5B50505050613B3A8E8E8E8E8E8E6104DD565B909F909E509C50505050505050505050505050565B60008060008342811015613B8F576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B613B9D8C8C8C8C8C8C615B37565B90945092506000613BCF7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8E8E614C3A565B9050613BDD8D338388614CC8565B613BE98C338387614CC8565B6040517F6A62784200000000000000000000000000000000000000000000000000000000815273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF821690636A62784290613C3B908A90600401617448565B602060405180830381600087803B158015613C5557600080FD5B505AF1158015613C69573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190613C8D9190616E5F565B92505050985098509895505050505050565B60008060008342811015613CDF576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B613D0D8A7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC28B348C8C615B37565B90945092506000613D5F7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8C7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2614C3A565B9050613D6D8B338388614CC8565B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663D0E30DB0856040518263FFFFFFFF1660E01B81526004016000604051808303818588803B158015613DD557600080FD5B505AF1158015613DE9573D6000803E3D6000FD5B50506040517FA9059CBB00000000000000000000000000000000000000000000000000000000815273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC216935063A9059CBB9250613E6291508490889060040161750F565B602060405180830381600087803B158015613E7C57600080FD5B505AF1158015613E90573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190613EB49190616D45565B613EBA57FE5B6040517F6A62784200000000000000000000000000000000000000000000000000000000815273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF821690636A62784290613F0C908A90600401617448565B602060405180830381600087803B158015613F2657600080FD5B505AF1158015613F3A573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190613F5E9190616E5F565B925083341115613F7457613F743385340361485C565B505096509650969350505050565B60608142811015613FBF576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175E7565B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168686600081811061400357FE5B90506020020160208101906140189190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614614065576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177A3565B6140C37F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8888888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061522292505050565B915034826000815181106140D357FE5B60200260200101511115614113576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906178CB565B61411B616889565B614159878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250339250614B2C915050565B90507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663D0E30DB0846000815181106141A457FE5B60200260200101516040518263FFFFFFFF1660E01B81526004016000604051808303818588803B1580156141D757600080FD5B505AF11580156141EB573D6000803E3D6000FD5B50505050507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663A9059CBB61425D7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8A8A6000818110611C2857FE5B8560008151811061426A57FE5B60200260200101516040518363FFFFFFFF1660E01B815260040161428F92919061750F565B602060405180830381600087803B1580156142A957600080FD5B505AF11580156142BD573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906142E19190616D45565B6142E757FE5B614326838888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508A9250614DF5915050565B80600001516020015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B15801561437457600080FD5B505AFA158015614388573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906143AC9190616E0B565B5082516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A0909101526020808201510151604080517F0902F1AC000000000000000000000000000000000000000000000000000000008152905173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90921691630902F1AC91600480820192606092909190829003018186803B15801561443E57600080FD5B505AFA158015614452573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906144769190616E0B565B5060208301516DFFFFFFFFFFFFFFFFFFFFFFFFFFFF91821660E0820152911660A09091015286866000816144A657FE5B90506020020160208101906144BB9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016144F39190617448565B60206040518083038186803B15801561450B57600080FD5B505AFA15801561451F573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906145439190616E5F565B60408201516020015286867FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF810181811061457A57FE5B905060200201602081019061458F9190616A7E565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231336040518263FFFFFFFF1660E01B81526004016145C79190617448565B60206040518083038186803B1580156145DF57600080FD5B505AFA1580156145F3573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906146179190616E5F565B816040015160600181815250507F7C35DBE99F3782224B7A94577650863AB74E9569C2A4E35EEF7B7D11FD29FA2A8160A00151338560008151811061465857FE5B602002602001015184606001518760018951038151811061467557FE5B602002602001015186608001516146C48E8E80806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250505060208B01518D9150614FDC565B8860C0015189604001516040516146E399989796959493929190617B30565B60405180910390A1826000815181106146F857FE5B602002602001015134111561472757614727338460008151811061471857FE5B6020026020010151340361485C565B505095945050505050565B600060608473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663A9059CBB858560405160240161476592919061750F565B6040516020818303038152906040529060E01B6020820180517BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83818316178352505050506040516147B39190617352565B6000604051808303816000865AF19150503D80600081146147F0576040519150601F19603F3D011682016040523D82523D6000602084013E6147F5565B606091505B509150915081801561481F57508051158061481F57508080602001905181019061481F9190616D45565B614855576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906175B0565B5050505050565B6040805160008082526020820190925273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84169083906040516148939190617352565B60006040518083038185875AF1925050503D80600081146148D0576040519150601F19603F3D011682016040523D82523D6000602084013E6148D5565B606091505B5050905080614910576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617985565B505050565B6000808411614950576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617AD3565B6000831180156149605750600082115B614996576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617811565B60006149AA856103E563FFFFFFFF615DF916565B905060006149BE828563FFFFFFFF615DF916565B905060006149E4836149D8886103E863FFFFFFFF615DF916565B9063FFFFFFFF615E4A16565B90508082816149EF57FE5B04979650505050505050565B6060600282511015614A39576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177DA565B815167FFFFFFFFFFFFFFFF81118015614A5157600080FD5B50604051908082528060200260200182016040528015614A7B578160200160208202803683370190505B5090508281600081518110614A8C57FE5B60200260200101818152505060005B6001835103811015614B2457600080614ADE87868581518110614ABA57FE5B6020026020010151878660010181518110614AD157FE5B6020026020010151615E87565B91509150614B00848481518110614AF157FE5B60200260200101518383614915565B848460010181518110614B0F57FE5B60209081029190910101525050600101614A9B565B509392505050565B614B34616889565B6040518060E00160405280614B7185600081518110614B4F57FE5B602002602001015186600181518110614B6457FE5B6020026020010151615F95565B8152602001614BA185600287510381518110614B8957FE5B602002602001015186600188510381518110614B6457FE5B8152602001614BDC8486600081518110614BB757FE5B602002602001015187600189510381518110614BCF57FE5B6020026020010151616228565B8152602001614BFE85600081518110614BF157FE5B6020026020010151616381565B8152602001614C1685600187510381518110614BF157FE5B8152602001614C23616606565B8152602001614C318561668D565B90529392505050565B6000806000614C498585615A38565B91509150858282604051602001614C61929190617318565B60405160208183030381529060405280519060200120604051602001614C889291906173C7565B604080517FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE081840301815291905280516020909101209695505050505050565B600060608573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166323B872DD868686604051602401614CFD939291906174B7565B6040516020818303038152906040529060E01B6020820180517BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8381831617835250505050604051614D4B9190617352565B6000604051808303816000865AF19150503D8060008114614D88576040519150601F19603F3D011682016040523D82523D6000602084013E614D8D565B606091505B5091509150818015614DB7575080511580614DB7575080806020019051810190614DB79190616D45565B614DED576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617A76565B505050505050565B60005B6001835103811015614FD657600080848381518110614E1357FE5B6020026020010151858460010181518110614E2A57FE5B6020026020010151915091506000614E428383615A38565B5090506000878560010181518110614E5657FE5B602002602001015190506000808373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168673FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614614E9E57826000614EA2565B6000835B91509150600060028A51038810614EB95788614EFA565B614EFA7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F878C8B60020181518110614EED57FE5B6020026020010151614C3A565B9050614F277F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8888614C3A565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663022C0D9F84848460006040519080825280601F01601F191660200182016040528015614F71576020820181803683370190505B506040518563FFFFFFFF1660E01B8152600401614F919493929190617C3C565B600060405180830381600087803B158015614FAB57600080FD5B505AF1158015614FBF573D6000803E3D6000FD5B505060019099019850614DF8975050505050505050565B50505050565B60007F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1682604001516040015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16148061509357507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1682606001516040015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16145B1561521A577F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16846001865103815181106150DF57FE5B602002602001015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16141561514157670DE0B6B3A76400006151146167E3565B8460018651038151811061512457FE5B602002602001015102620F4240028161513957FE5B04905061062D565B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1682604001516040015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614156151F3576305F5E100670DE0B6B3A76400006151B36167E3565B8460E001518560A00151816151C457FE5B04866001885103815181106151D557FE5B60200260200101510202620F424002816151EB57FE5B048161513957FE5B6305F5E100670DE0B6B3A76400006152096167E3565B8460A001518560E00151816151C457FE5B50600061062D565B6060600282511015615260576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906177DA565B815167FFFFFFFFFFFFFFFF8111801561527857600080FD5B506040519080825280602002602001820160405280156152A2578160200160208202803683370190505B50905082816001835103815181106152B657FE5B602090810291909101015281517FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF015B8015614B24576000806153168786600186038151811061530257FE5B6020026020010151878681518110614AD157FE5B9150915061533884848151811061532957FE5B602002602001015183836158B0565B84600185038151811061534757FE5B602090810291909101015250507FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF016152E6565B60005B60018351038110156149105760008084838151811061539957FE5B60200260200101518584600101815181106153B057FE5B60200260200101519150915060006153C88383615A38565B50905060006153F87F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8585614C3A565B90506000806000808473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B15801561544657600080FD5B505AFA15801561545A573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061547E9190616E0B565B506DFFFFFFFFFFFFFFFFFFFFFFFFFFFF1691506DFFFFFFFFFFFFFFFFFFFFFFFFFFFF1691506000808773FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168A73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16146154E05782846154E3565B83835B91509150615524828B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A082318A6040518263FFFFFFFF1660E01B81526004016132F49190617448565B9550615531868383614915565B9450505050506000808573FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168873FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF161461557557826000615579565B6000835B91509150600060028C51038A10615590578A6155C4565B6155C47F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F898E8D60020181518110614EED57FE5B604080516000815260208101918290527F022C0D9F0000000000000000000000000000000000000000000000000000000090915290915073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87169063022C0D9F9061562C9086908690869060248101617C3C565B600060405180830381600087803B15801561564657600080FD5B505AF115801561565A573D6000803E3D6000FD5B50506001909B019A5061537E9950505050505050505050565B80820382811115610DE1576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617579565B60007F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1682604001516040015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16148061576757507F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1682606001516040015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16145B1561521A577F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16846001865103815181106157B357FE5B602002602001015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614156157F657670DE0B6B3A76400006157E86167E3565B8402620F4240028161513957FE5B7F000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1682604001516040015173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF161415615889576305F5E100670DE0B6B3A76400006158686167E3565B8460E001518560A001518161587957FE5B04860202620F424002816151EB57FE5B6305F5E100670DE0B6B3A764000061589F6167E3565B8460A001518560E001518161587957FE5B60008084116158EB576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617655565B6000831180156158FB5750600082115B615931576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617811565B60006159556103E8615949868863FFFFFFFF615DF916565B9063FFFFFFFF615DF916565B9050600061596F6103E5615949868963FFFFFFFF61567316565B905061598C600182848161597F57FE5B049063FFFFFFFF615E4A16565B9695505050505050565B60008084116159D1576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B9061786E565B6000831180156159E15750600082115B615A17576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617811565B82615A28858463FFFFFFFF615DF916565B81615A2F57FE5B04949350505050565B6000808273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF161415615AA1576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906176E9565B8273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1610615ADB578284615ADE565B83835B909250905073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8216615B30576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617A3F565B9250929050565B6040517FE6A43905000000000000000000000000000000000000000000000000000000008152600090819081907F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF169063E6A4390590615BB2908C908C906004016174E8565B60206040518083038186803B158015615BCA57600080FD5B505AFA158015615BDE573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190615C029190616A9A565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF161415615CE6576040517FC9C6539600000000000000000000000000000000000000000000000000000000815273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F169063C9C6539690615C92908B908B906004016174E8565B602060405180830381600087803B158015615CAC57600080FD5B505AF1158015615CC0573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190615CE49190616A9A565B505B600080615D147F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8B8B615E87565B91509150816000148015615D26575080155B15615D3657879350869250615DEC565B6000615D43898484615996565B9050878111615D915785811015615D86576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617746565B889450925082615DEA565B6000615D9E898486615996565B905089811115615DAA57FE5B87811015615DE4576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B90617928565B94508793505B505B5050965096945050505050565B6000811580615E1457505080820282828281615E1157FE5B04145B610DE1576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B9061761E565B80820182811015610DE1576040517F08C379A000000000000000000000000000000000000000000000000000000000815260040161051B906176B2565B6000806000615E968585615A38565B509050600080615EA7888888614C3A565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B158015615EEC57600080FD5B505AFA158015615F00573D6000803E3D6000FD5B505050506040513D601F19601F82011682018060405250810190615F249190616E0B565B506DFFFFFFFFFFFFFFFFFFFFFFFFFFFF1691506DFFFFFFFFFFFFFFFFFFFFFFFFFFFF1691508273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF168773FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1614615F83578082615F86565B81815B90999098509650505050505050565B615F9D616909565B6000615FCA7F0000000000000000000000005C69BEE701EF814A2B6A3EDD4B1652CB9CC5AA6F8585614C3A565B9050600081905060008173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630DFE16816040518163FFFFFFFF1660E01B815260040160206040518083038186803B15801561601957600080FD5B505AFA15801561602D573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906160519190616A9A565B905060008273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663D21220A76040518163FFFFFFFF1660E01B815260040160206040518083038186803B15801561609B57600080FD5B505AFA1580156160AF573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906160D39190616A9A565B90506000808473FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16630902F1AC6040518163FFFFFFFF1660E01B815260040160606040518083038186803B15801561611E57600080FD5B505AFA158015616132573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906161569190616E0B565B506DFFFFFFFFFFFFFFFFFFFFFFFFFFFF1691506DFFFFFFFFFFFFFFFFFFFFFFFFFFFF169150616183616959565B61618C85616381565B9050616196616959565B61619F85616381565B60408051610100810190915283518251929350909182916161C491610120840161736E565B60405160208183030381529060405281526020018973FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1681526020018381526020018281526020018581526020016000815260200184815260200160008152509850505050505050505092915050565B61623061699E565B6040805160808101918290527F70A08231000000000000000000000000000000000000000000000000000000009091528073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85166370A0823161628A8860848501617448565B60206040518083038186803B1580156162A257600080FD5B505AFA1580156162B6573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906162DA9190616E5F565B8152602001600081526020018373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166370A08231876040518263FFFFFFFF1660E01B815260040161631F9190617448565B60206040518083038186803B15801561633757600080FD5B505AFA15801561634B573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061636F9190616E5F565B81526000602090910152949350505050565B616389616959565B6040518060A001604052808373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166395D89B416040518163FFFFFFFF1660E01B815260040160006040518083038186803B1580156163DA57600080FD5B505AFA1580156163EE573D6000803E3D6000FD5B505050506040513D6000823E601F3D9081017FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE01682016040526164349190810190616D61565B81526020018373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166306FDDE036040518163FFFFFFFF1660E01B815260040160006040518083038186803B15801561647F57600080FD5B505AFA158015616493573D6000803E3D6000FD5B505050506040513D6000823E601F3D9081017FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE01682016040526164D99190810190616D61565B81526020018373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1681526020018373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF166318160DDD6040518163FFFFFFFF1660E01B815260040160206040518083038186803B15801561654057600080FD5B505AFA158015616554573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906165789190616E5F565B81526020018373FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663313CE5676040518163FFFFFFFF1660E01B815260040160206040518083038186803B1580156165C357600080FD5B505AFA1580156165D7573D6000803E3D6000FD5B505050506040513D601F19601F820116820180604052508101906165FB91906170A3565B60FF16905292915050565B61660E6169C6565B506040805160A0810182526007606082019081527F556E6973776170000000000000000000000000000000000000000000000000006080830152815281518083018352600281527F7632000000000000000000000000000000000000000000000000000000000000602082810191909152820152309181019190915290565B6166956169FD565B6060825167FFFFFFFFFFFFFFFF811180156166AF57600080FD5B506040519080825280602002602001820160405280156166E957816020015B6166D6616959565B8152602001906001900390816166CE5790505B5090506060600184510367FFFFFFFFFFFFFFFF8111801561670957600080FD5B5060405190808252806020026020018201604052801561674357816020015B616730616909565B8152602001906001900390816167285790505B50905060005B84518110156167CA57616761858281518110614BF157FE5B83828151811061676D57FE5B602002602001018190525060018551038110156167C2576167AA85828151811061679357FE5B6020026020010151868360010181518110614B6457FE5B8282815181106167B657FE5B60200260200101819052505B600101616749565B5060408051808201909152918252602082015292915050565B600080735F4EC3DF9CBD43714FE2740F5E3616155C5B8419905060008173FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1663FEAF968C6040518163FFFFFFFF1660E01B815260040160A06040518083038186803B15801561684557600080FD5B505AFA158015616859573D6000803E3D6000FD5B505050506040513D601F19601F8201168201806040525081019061687D9190617052565B50919550505050505090565B6040518060E0016040528061689C616909565B81526020016168A9616909565B81526020016168B661699E565B81526020016168C3616959565B81526020016168D0616959565B81526020016168DD6169C6565B81526020016168EA6169FD565B905290565B604051806040016040528060008152602001600081525090565B6040805161010081018252606081526000602082015290810161692A616959565B8152602001616937616959565B8152602001600081526020016000815260200160008152602001600081525090565B6040518060A001604052806060815260200160608152602001600073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16815260200160008152602001600081525090565B6040518060800160405280600081526020016000815260200160008152602001600081525090565B60405180606001604052806060815260200160608152602001600073FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1681525090565B604051806040016040528060608152602001606081525090565B8035610DE181617CE0565B60008083601F840112616A33578182FD5B50813567FFFFFFFFFFFFFFFF811115616A4A578182FD5B6020830191508360208083028501011115615B3057600080FD5B805169FFFFFFFFFFFFFFFFFFFF81168114610DE157600080FD5B600060208284031215616A8F578081FD5B813561062D81617CE0565B600060208284031215616AAB578081FD5B815161062D81617CE0565B600080600080600080600060E0888A031215616AD0578283FD5B8735616ADB81617CE0565B96506020880135616AEB81617CE0565B955060408801359450606088013593506080880135925060A0880135616B1081617CE0565B8092505060C0880135905092959891949750929550565B60008060008060008060008060008060006101608C8E031215616B48578384FD5B8B35616B5381617CE0565B9A5060208C0135616B6381617CE0565B995060408C0135985060608C0135975060808C0135965060A08C0135616B8881617CE0565B955060C08C0135945060E08C0135616B9F81617D05565B93506101008C0135616BB081617D2F565B809350506101208C013591506101408C013590509295989B509295989B9093969950565B600080600080600080600080610100898B031215616BF0578384FD5B8835616BFB81617CE0565B97506020890135616C0B81617CE0565B965060408901359550606089013594506080890135935060A0890135925060C0890135616C3781617CE0565B8092505060E089013590509295985092959890939650565B60008060008060008060C08789031215616C67578384FD5B8635616C7281617CE0565B95506020870135945060408701359350606087013592506080870135616C9781617CE0565B8092505060A087013590509295509295509295565B6000806000806000806000806000806101408B8D031215616CCB578384FD5B8A35616CD681617CE0565B995060208B0135985060408B0135975060608B0135965060808B0135616CFB81617CE0565B955060A08B0135945060C08B0135616D1281617D05565B935060E08B0135616D2281617D2F565B809350506101008B013591506101208B013590509295989B9194979A5092959850565B600060208284031215616D56578081FD5B815161062D81617D05565B600060208284031215616D72578081FD5B815167FFFFFFFFFFFFFFFF80821115616D89578283FD5B81840185601F820112616D9A578384FD5B8051925081831115616DAA578384FD5B616DDB60207FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0601F86011601617C8D565B9150828252856020848301011115616DF1578384FD5B616E02836020840160208401617CB4565B50949350505050565B600080600060608486031215616E1F578081FD5B8351616E2A81617D13565B6020850151909350616E3B81617D13565B604085015190925063FFFFFFFF81168114616E54578182FD5B809150509250925092565B600060208284031215616E70578081FD5B5051919050565B600080600080600060808688031215616E8E578283FD5B85359450602086013567FFFFFFFFFFFFFFFF811115616EAB578384FD5B616EB788828901616A22565B9095509350506040860135616ECB81617CE0565B949793965091946060013592915050565B60008060408385031215616EEE578182FD5B8235915060208084013567FFFFFFFFFFFFFFFF80821115616F0D578384FD5B81860187601F820112616F1E578485FD5B8035925081831115616F2E578485FD5B8383029150616F3E848301617C8D565B8381528481019082860184840187018B1015616F58578788FD5B8794505B85851015616F8257616F6E8B82616A17565B835260019490940193918601918601616F5C565B508096505050505050509250929050565B60008060408385031215616FA5578182FD5B505080516020909101519092909150565B60008060008060008060A08789031215616FCE578384FD5B8635955060208701359450604087013567FFFFFFFFFFFFFFFF811115616FF2578485FD5B616FFE89828A01616A22565B909550935050606087013561701281617CE0565B80925050608087013590509295509295509295565B60008060006060848603121561703B578081FD5B505081359360208301359350604090920135919050565B600080600080600060A08688031215617069578283FD5B6170738787616A64565B94506020860151935060408601519250606086015191506170978760808801616A64565B90509295509295909350565B6000602082840312156170B4578081FD5B815161062D81617D2F565B600061010082518185526170D582860182617178565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF602086015116602087015260408501519250858103604087015261710E81846171C2565B91505060608401519150848103606086015261712A81836171C2565B6080850151608087015260A085015160A087015260C085015160C087015260E085015160E087015280935050505092915050565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF169052565B60008151808452617190816020860160208601617CB4565B601F017FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0169290920160200192915050565B6000815160A084526171D760A0850182617178565B6020840151915084810360208601526171F08183617178565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6040860151166040870152606085015160608701526080850151608087015280935050505092915050565B6000604083018251604085528181516172498185617C25565B9150819350602080820283018185018795505B838610156172885786820385526172748282516171C2565B60019690960195948301949150820161725C565B50818801519550888103828A0152809450855193506172A78482617C25565B94505083915080830284018186018796505B848710156172E55783820386526172D18282516170BF565B6001979097019695830195915082016172B9565B5098975050505050505050565B805182526020810151602083015260408101516040830152606081015160608301525050565B7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000606093841B811682529190921B16601482015260280190565B60008251617364818460208701617CB4565B9190910192915050565B60008351617380818460208801617CB4565B8083017F2D000000000000000000000000000000000000000000000000000000000000008152845191506173BB826001830160208801617CB4565B01600101949350505050565B7FFF00000000000000000000000000000000000000000000000000000000000000815260609290921B7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000016600183015260158201527F96E8AC4277198FF8B6F785478AA9A39F403CB768DD02CBEE326C3E7DA348845F603582015260550190565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91909116815260200190565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97881681529590961660208601526040850193909352606084019190915260FF16608083015260A082015260C081019190915260E00190565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9384168152919092166020820152604081019190915260600190565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92831681529116602082015260400190565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF929092168252602082015260400190565B6020808252825182820181905260009190848201906040850190845B8181101561756D57835183529284019291840191600101617551565B50909695505050505050565B60208082526015908201527F64732D6D6174682D7375622D756E646572666C6F770000000000000000000000604082015260600190565B6020808252601F908201527F5472616E7366657248656C7065723A205452414E534645525F4641494C454400604082015260600190565B60208082526018908201527F556E69737761705632526F757465723A20455850495245440000000000000000604082015260600190565B60208082526014908201527F64732D6D6174682D6D756C2D6F766572666C6F77000000000000000000000000604082015260600190565B6020808252602C908201527F556E697377617056324C6962726172793A20494E53554646494349454E545F4F60408201527F55545055545F414D4F554E540000000000000000000000000000000000000000606082015260800190565B60208082526014908201527F64732D6D6174682D6164642D6F766572666C6F77000000000000000000000000604082015260600190565B60208082526025908201527F556E697377617056324C6962726172793A204944454E544943414C5F4144445260408201527F4553534553000000000000000000000000000000000000000000000000000000606082015260800190565B60208082526026908201527F556E69737761705632526F757465723A20494E53554646494349454E545F425F60408201527F414D4F554E540000000000000000000000000000000000000000000000000000606082015260800190565B6020808252601D908201527F556E69737761705632526F757465723A20494E56414C49445F50415448000000604082015260600190565B6020808252601E908201527F556E697377617056324C6962726172793A20494E56414C49445F504154480000604082015260600190565B60208082526028908201527F556E697377617056324C6962726172793A20494E53554646494349454E545F4C60408201527F4951554944495459000000000000000000000000000000000000000000000000606082015260800190565B60208082526025908201527F556E697377617056324C6962726172793A20494E53554646494349454E545F4160408201527F4D4F554E54000000000000000000000000000000000000000000000000000000606082015260800190565B60208082526027908201527F556E69737761705632526F757465723A204558434553534956455F494E50555460408201527F5F414D4F554E5400000000000000000000000000000000000000000000000000606082015260800190565B60208082526026908201527F556E69737761705632526F757465723A20494E53554646494349454E545F415F60408201527F414D4F554E540000000000000000000000000000000000000000000000000000606082015260800190565B60208082526023908201527F5472616E7366657248656C7065723A204554485F5452414E534645525F46414960408201527F4C45440000000000000000000000000000000000000000000000000000000000606082015260800190565B6020808252602B908201527F556E69737761705632526F757465723A20494E53554646494349454E545F4F5560408201527F545055545F414D4F554E54000000000000000000000000000000000000000000606082015260800190565B6020808252601E908201527F556E697377617056324C6962726172793A205A45524F5F414444524553530000604082015260600190565B60208082526024908201527F5472616E7366657248656C7065723A205452414E534645525F46524F4D5F464160408201527F494C454400000000000000000000000000000000000000000000000000000000606082015260800190565B6020808252602B908201527F556E697377617056324C6962726172793A20494E53554646494349454E545F4960408201527F4E5055545F414D4F554E54000000000000000000000000000000000000000000606082015260800190565B60006101808083528B51606082850152617B4E6101E0850182617178565B91505060208C01517FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE80848303016101A0850152617B8B8282617178565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60408F0151166101C0860152617BB9602086018E61715E565B8B60408601528481036060860152617BD1818C6171C2565B9250505087608084015282810360A0840152617BED81886171C2565B8660C085015283810360E0850152617C058187617230565B92505050617C176101008301846172F2565B9A9950505050505050505050565B90815260200190565B918252602082015260400190565B600085825284602083015273FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF841660408301526080606083015261598C6080830184617178565B9283526020830191909152604082015260600190565B60405181810167FFFFFFFFFFFFFFFF81118282101715617CAC57600080FD5B604052919050565B60005B83811015617CCF578181015183820152602001617CB7565B83811115614FD65750506000910152565B73FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81168114617D0257600080FD5B50565B8015158114617D0257600080FD5B6DFFFFFFFFFFFFFFFFFFFFFFFFFFFF81168114617D0257600080FD5B60FF81168114617D0257600080FDFEA264697066735822122017859CF77A0030EF35C76B608F559E9AB68F969BBEBFF3FB5B786957E7783D1B64736F6C63430006060033" 4 | } 5 | --------------------------------------------------------------------------------