diff --git a/.cargo/config.toml b/.cargo/config.toml index 1ca035a75d78c..ca844fb33e15b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,12 @@ [alias] -cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +spec-config = "test -p foundry-config-spec --features schema tests::" test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" bless-lints = "test -p forge --test ui -- --bless" +# Backwards compatibility alias for `spec-cheats` +cheats = "spec-cheats" + # Increase the stack size to 10MB for Windows targets, which is in line with Linux # (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] diff --git a/.circleci/cargo.yml b/.circleci/cargo.yml new file mode 100644 index 0000000000000..32b65e6a23cc5 --- /dev/null +++ b/.circleci/cargo.yml @@ -0,0 +1,32 @@ +version: 2.1 +# +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci-web3-gamefi.yml b/.circleci/ci-web3-gamefi.yml new file mode 100644 index 0000000000000..ad53a8e498202 --- /dev/null +++ b/.circleci/ci-web3-gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.circleci/ci.yml b/.circleci/ci.yml new file mode 100644 index 0000000000000..1b5df6d6e668e --- /dev/null +++ b/.circleci/ci.yml @@ -0,0 +1,31 @@ +version: 2.1 +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test diff --git a/.circleci/ci_cargo.yml b/.circleci/ci_cargo.yml new file mode 100644 index 0000000000000..46a18d45a5fca --- /dev/null +++ b/.circleci/ci_cargo.yml @@ -0,0 +1,37 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.88.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/ci_v1.yml b/.circleci/ci_v1.yml new file mode 100644 index 0000000000000..82c6de5b42b73 --- /dev/null +++ b/.circleci/ci_v1.yml @@ -0,0 +1,31 @@ +version: 2.1 + +jobs: + build-and-test: + docker: + - image: cimg/rust:1.89.0 + steps: + - checkout + - restore_cache: + keys: + - v1-cargo-{{ checksum "Cargo.lock" }} + - v1-cargo- + - run: + name: "Check formatting" + command: cargo fmt -- --check + - run: + name: "Run tests" + command: cargo test + - save_cache: + key: v1-cargo-{{ checksum "Cargo.lock" }} + paths: + - "~/.cargo/bin" + - "~/.cargo/registry/index" + - "~/.cargo/registry/cache" + - "~/.cargo/git/db" + - "target" + +workflows: + ci: + jobs: + - build-and-test diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000000..2ef62819f07dc --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,22 @@ +version: 2.1 + +jobs: + test: + docker: + - image: ghcr.io/foundry-rs/foundry:latest + steps: + - checkout + - run: + name: Install submodules + command: git submodule update --init --recursive + - run: + name: Build + command: forge build + - run: + name: Test + command: forge test -vvv + +workflows: + main: + jobs: + - test diff --git a/.circleci/dev_stage.yml b/.circleci/dev_stage.yml new file mode 100644 index 0000000000000..5ba351727d22b --- /dev/null +++ b/.circleci/dev_stage.yml @@ -0,0 +1,70 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- + + jobs: + my-job: + steps: + - run: echo "Hello, world!" + - run: + command: echo "This step will automatically rerun up to 3 times if it fails with a 10 second delay between attempts" + max_auto_reruns: 3 + auto_rerun_delay: 10s + + workflows: + dev_stage_pre-prod: + jobs: + - test_dev: + filters: # using regex filters requires the entire branch to match + branches: + only: # only branches matching the below regex filters will run + - dev + - /user-.*/ + - test_stage: + filters: + branches: + only: stage + - test_pre-prod: + filters: + branches: + only: /pre-prod(?:-.+)?$/ + + + build-test-deploy: + jobs: + - build: + filters: # required since `test` has tag filters AND requires `build` + tags: + only: /^config-test.*/ + - test: + requires: + - build + filters: # required since `deploy` has tag filters AND requires `test` + tags: + only: /^config-test.*/ + - deploy: + requires: + - test + filters: + tags: + only: /^config-test.*/ + branches: + ignore: /.*/ diff --git a/.circleci/web3_defi_gamefi.yml b/.circleci/web3_defi_gamefi.yml new file mode 100644 index 0000000000000..edb6605e3f101 --- /dev/null +++ b/.circleci/web3_defi_gamefi.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/configuration-reference + +version: 2.1 +executors: + my-custom-executor: + docker: + - image: cimg/base:stable + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/github/Dargon789/foundry/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD +jobs: + web3-defi-game-project-: + + executor: my-custom-executor + steps: + - checkout + - run: | + # echo Hello, World! + +workflows: + my-custom-workflow: + jobs: + - web3-defi-game-project- diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json new file mode 100644 index 0000000000000..b34104d5de54e --- /dev/null +++ b/.codesandbox/tasks.json @@ -0,0 +1,7 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": {} +} diff --git a/.deps/remix-tests/remix_accounts.sol b/.deps/remix-tests/remix_accounts.sol new file mode 100644 index 0000000000000..c1c42dc96b93e --- /dev/null +++ b/.deps/remix-tests/remix_accounts.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library TestsAccounts { + function getAccount(uint index) pure public returns (address) { + address[15] memory accounts; + accounts[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; + + accounts[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; + + accounts[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db; + + accounts[3] = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB; + + accounts[4] = 0x617F2E2fD72FD9D5503197092aC168c91465E7f2; + + accounts[5] = 0x17F6AD8Ef982297579C203069C1DbfFE4348c372; + + accounts[6] = 0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678; + + accounts[7] = 0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7; + + accounts[8] = 0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C; + + accounts[9] = 0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC; + + accounts[10] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + accounts[11] = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C; + + accounts[12] = 0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB; + + accounts[13] = 0x583031D1113aD414F02576BD6afaBfb302140225; + + accounts[14] = 0xdD870fA1b7C4700F2BD7f44238821C26f7392148; +return accounts[index]; + } +} diff --git a/.deps/remix-tests/remix_tests.sol b/.deps/remix-tests/remix_tests.sol new file mode 100644 index 0000000000000..b8b9960362203 --- /dev/null +++ b/.deps/remix-tests/remix_tests.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.4.22 <0.9.0; + +library Assert { + + event AssertionEvent( + bool passed, + string message, + string methodName + ); + + event AssertionEventUint( + bool passed, + string message, + string methodName, + uint256 returned, + uint256 expected + ); + + event AssertionEventInt( + bool passed, + string message, + string methodName, + int256 returned, + int256 expected + ); + + event AssertionEventBool( + bool passed, + string message, + string methodName, + bool returned, + bool expected + ); + + event AssertionEventAddress( + bool passed, + string message, + string methodName, + address returned, + address expected + ); + + event AssertionEventBytes32( + bool passed, + string message, + string methodName, + bytes32 returned, + bytes32 expected + ); + + event AssertionEventString( + bool passed, + string message, + string methodName, + string returned, + string expected + ); + + event AssertionEventUintInt( + bool passed, + string message, + string methodName, + uint256 returned, + int256 expected + ); + + event AssertionEventIntUint( + bool passed, + string message, + string methodName, + int256 returned, + uint256 expected + ); + + function ok(bool a, string memory message) public returns (bool result) { + result = a; + emit AssertionEvent(result, message, "ok"); + } + + function equal(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventUint(result, message, "equal", a, b); + } + + function equal(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventInt(result, message, "equal", a, b); + } + + function equal(bool a, bool b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBool(result, message, "equal", a, b); + } + + // TODO: only for certain versions of solc + //function equal(fixed a, fixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function equal(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a == b); + // emit AssertionEvent(result, message); + //} + + function equal(address a, address b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventAddress(result, message, "equal", a, b); + } + + function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a == b); + emit AssertionEventBytes32(result, message, "equal", a, b); + } + + function equal(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "equal", a, b); + } + + function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventUint(result, message, "notEqual", a, b); + } + + function notEqual(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventInt(result, message, "notEqual", a, b); + } + + function notEqual(bool a, bool b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBool(result, message, "notEqual", a, b); + } + + // TODO: only for certain versions of solc + //function notEqual(fixed a, fixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + // TODO: only for certain versions of solc + //function notEqual(ufixed a, ufixed b, string message) public returns (bool result) { + // result = (a != b); + // emit AssertionEvent(result, message); + //} + + function notEqual(address a, address b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventAddress(result, message, "notEqual", a, b); + } + + function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) { + result = (a != b); + emit AssertionEventBytes32(result, message, "notEqual", a, b); + } + + function notEqual(string memory a, string memory b, string memory message) public returns (bool result) { + result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))); + emit AssertionEventString(result, message, "notEqual", a, b); + } + + /*----------------- Greater than --------------------*/ + function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventUint(result, message, "greaterThan", a, b); + } + + function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a > b); + emit AssertionEventInt(result, message, "greaterThan", a, b); + } + // TODO: safely compare between uint and int + function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative uint "a" always greater + result = true; + } else { + result = (a > uint(b)); + } + emit AssertionEventUintInt(result, message, "greaterThan", a, b); + } + function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative uint "b" always greater + result = false; + } else { + result = (uint(a) > b); + } + emit AssertionEventIntUint(result, message, "greaterThan", a, b); + } + /*----------------- Lesser than --------------------*/ + function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventUint(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) { + result = (a < b); + emit AssertionEventInt(result, message, "lesserThan", a, b); + } + // TODO: safely compare between uint and int + function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) { + if(b < int(0)) { + // int is negative int "b" always lesser + result = false; + } else { + result = (a < uint(b)); + } + emit AssertionEventUintInt(result, message, "lesserThan", a, b); + } + + function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) { + if(a < int(0)) { + // int is negative int "a" always lesser + result = true; + } else { + result = (uint(a) < b); + } + emit AssertionEventIntUint(result, message, "lesserThan", a, b); + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..edd3e4a15ddbc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,41 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. Chrome, Safari] + - Version [e.g. 22] + - Browser [e.g. Chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, Safari] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000000..48d5f81fa4229 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..bbcbbe7d61558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/scripts/bump-tempo.sh b/.github/scripts/bump-tempo.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/commit-benchmark-results.sh b/.github/scripts/commit-benchmark-results.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/compare-nightly.sh b/.github/scripts/compare-nightly.sh index 788132b5c7ed9..549c4c80e846b 100755 --- a/.github/scripts/compare-nightly.sh +++ b/.github/scripts/compare-nightly.sh @@ -19,7 +19,10 @@ warn = float(os.environ["WARN"]) fail = float(os.environ["FAIL"]) prev_path = os.environ.get("PREV_JSON", "") -prev = json.load(open(prev_path)) if prev_path and os.path.isfile(prev_path) else {} +prev = {} +if prev_path and os.path.isfile(prev_path): + with open(prev_path) as f: + prev = json.load(f) with open(os.environ["TODAY_JSON"]) as f: today = json.load(f) @@ -39,7 +42,10 @@ for key in all_keys: if p is None: print(f"| `{key}` | N/A | {t:.5f}s | — | 🆕 New |") continue - delta = (t - p) / p * 100 + if p == 0: + delta = float('inf') if t > 0 else 0.0 + else: + delta = (t - p) / p * 100 if delta >= fail: status = "🔴 Regression" has_regression = True diff --git a/.github/scripts/read-benchmark-results.sh b/.github/scripts/read-benchmark-results.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-check.sh b/.github/scripts/tempo-check.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-deploy.sh b/.github/scripts/tempo-deploy.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-mpp.sh b/.github/scripts/tempo-mpp.sh old mode 100755 new mode 100644 diff --git a/.github/scripts/tempo-wallet.sh b/.github/scripts/tempo-wallet.sh old mode 100755 new mode 100644 diff --git a/.github/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..7b85ca2ae00c8 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "master" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/apisec-scan.yml b/.github/workflows/apisec-scan.yml new file mode 100644 index 0000000000000..e716760284792 --- /dev/null +++ b/.github/workflows/apisec-scan.yml @@ -0,0 +1,29 @@ +name: APIsec +permissions: + contents: read + +on: + pull_request: + branches: + - main + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run APIsec scan + uses: apisec-inc/apisec-run-scan@025432089674a28ba8fb55f8ab06c10215e772ea + with: + apisec-username: ${{ secrets.APISEC_USERNAME }} + apisec-password: ${{ secrets.APISEC_PASSWORD }} + apisec-project: VAmPI + apisec-profile: Master + apisec-region: us-east-1 + sarif-result-file: apisec-results.sarif + apisec-email-report: true + apisec-fail-on-vuln-severity: critical + apisec-oas: false + apisec-openapi-spec-url: "https://example.com/openapi.json" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000000..5bf742c565e0f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,92 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '25 9 * * 3' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000000..1ab3e63e39815 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,27 @@ +name: Foundry Build & Deploy +permissions: + contents: read +on: + push: + branches: [main, master] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Build project + run: cargo build --release + + - name: Run tests + run: cargo test + + - name: Docker build + run: docker build -t foundryg-rs diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000000000..e0c9c518e8748 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,21 @@ +name: Docker Image CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag my-image-name:${{ github.sha }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..7b85ca2ae00c8 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "master" + pull_request: + branches: ["**"] + +env: + # Hostname of your registry + REGISTRY: docker.io + # Image repository, without hostname and tag + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + # Authenticate to the container registry + - name: Authenticate to registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + # Extract metadata (tags, labels) for Docker + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx + # (don't push on PR, load instead) + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + sbom: ${{ github.event_name != 'pull_request' }} + provenance: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml new file mode 100644 index 0000000000000..569c7b00a4b7f --- /dev/null +++ b/.github/workflows/google.yml @@ -0,0 +1,117 @@ +# This workflow will build a docker container, publish it to Google Container +# Registry, and deploy it to GKE when there is a push to the "main" +# branch. +# +# To configure this workflow: +# +# 1. Enable the following Google Cloud APIs: +# +# - Artifact Registry (artifactregistry.googleapis.com) +# - Google Kubernetes Engine (container.googleapis.com) +# - IAM Credentials API (iamcredentials.googleapis.com) +# +# You can learn more about enabling APIs at +# https://support.google.com/googleapi/answer/6158841. +# +# 2. Ensure that your repository contains the necessary configuration for your +# Google Kubernetes Engine cluster, including deployment.yml, +# kustomization.yml, service.yml, etc. +# +# 3. Create and configure a Workload Identity Provider for GitHub: +# https://github.com/google-github-actions/auth#preferred-direct-workload-identity-federation. +# +# Depending on how you authenticate, you will need to grant an IAM principal +# permissions on Google Cloud: +# +# - Artifact Registry Administrator (roles/artifactregistry.admin) +# - Kubernetes Engine Developer (roles/container.developer) +# +# You can learn more about setting IAM permissions at +# https://cloud.google.com/iam/docs/manage-access-other-resources +# +# 5. Change the values in the "env" block to match your values. + +name: 'Build and Deploy to GKE' + +on: + push: + branches: + - '"main"' + - '"master"' + +env: + PROJECT_ID: 'my-project' # TODO: update to your Google Cloud project ID + GAR_LOCATION: 'us-central1' # TODO: update to your region + GKE_CLUSTER: 'cluster-1' # TODO: update to your cluster name + GKE_ZONE: 'us-central1-c' # TODO: update to your cluster zone + DEPLOYMENT_NAME: 'gke-test' # TODO: update to your deployment name + REPOSITORY: 'samples' # TODO: update to your Artifact Registry docker repository name + IMAGE: 'static-site' + WORKLOAD_IDENTITY_PROVIDER: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' # TODO: update to your workload identity provider + +jobs: + setup-build-publish-deploy: + name: 'Setup, Build, Publish, and Deploy' + runs-on: 'ubuntu-latest' + environment: 'production' + + permissions: + contents: 'read' + id-token: 'write' + + steps: + - name: 'Checkout' + uses: 'actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332' # actions/checkout@v4 + + # Configure Workload Identity Federation and generate an access token. + # + # See https://github.com/google-github-actions/auth for more options, + # including authenticating via a JSON credentials file. + - id: 'auth' + name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@f112390a2df9932162083945e46d439060d66ec2' # google-github-actions/auth@v2 + with: + workload_identity_provider: '${{ env.WORKLOAD_IDENTITY_PROVIDER }}' + + # Authenticate Docker to Google Cloud Artifact Registry + - name: 'Docker Auth' + uses: 'docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567' # docker/login-action@v3 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.auth_token }}' + registry: '${{ env.GAR_LOCATION }}-docker.pkg.dev' + + # Get the GKE credentials so we can deploy to the cluster + - name: 'Set up GKE credentials' + uses: 'google-github-actions/get-gke-credentials@3da1e46a907576cefaa90c484278bb5b259dd395' # google-github-actions/get-gke-credentials@v2 + with: + cluster_name: '${{ env.GKE_CLUSTER }}' + location: '${{ env.GKE_ZONE }}' + + # Build the Docker image + - name: 'Build and push Docker container' + run: |- + DOCKER_TAG="${GAR_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${GITHUB_SHA}" + + docker build \ + --tag "${DOCKER_TAG}" \ + --build-arg GITHUB_SHA="${GITHUB_SHA}" \ + --build-arg GITHUB_REF="${GITHUB_REF}" \ + . + + docker push "${DOCKER_TAG}" + + # Set up kustomize + - name: 'Set up Kustomize' + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: 'Deploy to GKE' + run: |- + # replacing the image name in the k8s template + ./kustomize edit set image LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:TAG=$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/snyk-container.yml b/.github/workflows/snyk-container.yml new file mode 100644 index 0000000000000..f07df9c75c8d1 --- /dev/null +++ b/.github/workflows/snyk-container.yml @@ -0,0 +1,55 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# A sample workflow which checks out the code, builds a container +# image using Docker and scans that image for vulnerabilities using +# Snyk. The results are then uploaded to GitHub Security Code Scanning +# +# For more examples, including how to limit scans to only high-severity +# issues, monitor images for newly disclosed vulnerabilities in Snyk and +# fail PR checks for new vulnerabilities, see https://github.com/snyk/actions/ + +name: Snyk Container + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '30 10 * * 1' + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Build a Docker image + run: docker build -t your/image-to-test . + - name: Run Snyk to check Docker image for vulnerabilities + # Snyk can be used to break the build when it detects vulnerabilities. + # In this case we want to upload the issues to GitHub Code Scanning + continue-on-error: true + uses: snyk/actions/docker@9adf32b1121593767fc3c057af55b55db032dc04 + env: + # In order to use the Snyk Action you will need to have a Snyk API token. + # More details in https://github.com/snyk/actions#getting-your-snyk-token + # or you can signup for free at https://snyk.io/login + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: your/image-to-test + args: --file=Dockerfile + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: snyk.sarif diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000000000..fcfbbbbec7948 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v6 + - name: Upload artifact + uses: actions/upload-pages-artifact@v5 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000..b1269653d9c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "counter/lib/forge-std"] + path = counter/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "counter/lib/openzeppelin-contracts"] + path = counter/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 50c7afae2ddec..8faaa8c87ccff 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -138,13 +138,24 @@ impl BenchmarkProject { let root = root_path.to_str().unwrap(); // Remove all files in the directory + let root_path = root_path.canonicalize()?; for entry in std::fs::read_dir(&root_path)? { let entry = entry?; let path = entry.path(); - if path.is_dir() { - std::fs::remove_dir_all(&path).ok(); + // Canonicalize the entry to prevent directory traversal + let canon = match path.canonicalize() { + Ok(p) => p, + Err(_) => continue, // Skip if unable to canonicalize + }; + // Ensure canonicalized path stays strictly within root_path (TempProject root) + if !canon.starts_with(root_path.canonicalize().unwrap_or_else(|_| root_path.clone())) { + sh_eprintln!("âš ī¸ Skipping suspicious path during cleanup: {:?}", canon); + continue; + } + if canon.is_dir() { + std::fs::remove_dir_all(&canon).ok(); } else { - std::fs::remove_file(&path).ok(); + std::fs::remove_file(&canon).ok(); } } diff --git a/counter/.gas-snapshot b/counter/.gas-snapshot new file mode 100644 index 0000000000000..797ceebb2f595 --- /dev/null +++ b/counter/.gas-snapshot @@ -0,0 +1,2 @@ +CounterTest:testFuzz_SetNumber(uint256) (runs: 256, Îŧ: 30410, ~: 32354) +CounterTest:test_Increment() (gas: 31851) \ No newline at end of file diff --git a/counter/.github/workflows/test.yml b/counter/.github/workflows/test.yml new file mode 100644 index 0000000000000..34a4a527be6f9 --- /dev/null +++ b/counter/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/counter/.gitignore b/counter/.gitignore new file mode 100644 index 0000000000000..052b88bb6516b --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,18 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env + + +# Soldeer +/dependencies diff --git a/counter/README.md b/counter/README.md new file mode 100644 index 0000000000000..679a7f4518035 --- /dev/null +++ b/counter/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose Solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/counter/doc/doc-filelist.js b/counter/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/counter/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/counter/doc/doc-script.js b/counter/doc/doc-script.js new file mode 100644 index 0000000000000..62eeda3efc308 --- /dev/null +++ b/counter/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +let sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/counter/doc/doc-style.css b/counter/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/counter/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} diff --git a/counter/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..c27b8ed21ba0b --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,9 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib", "dependencies"] + +[dependencies] +forge-std = "1.15.0" + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/counter/lib/forge-std b/counter/lib/forge-std new file mode 160000 index 0000000000000..3b20d60d14b34 --- /dev/null +++ b/counter/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/counter/lib/openzeppelin-contracts b/counter/lib/openzeppelin-contracts new file mode 160000 index 0000000000000..ca7a4e39de086 --- /dev/null +++ b/counter/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ca7a4e39de0860bbaadf95824207886e6de9fa64 diff --git a/counter/remappings.txt b/counter/remappings.txt new file mode 100644 index 0000000000000..6c93bbb0c4b12 --- /dev/null +++ b/counter/remappings.txt @@ -0,0 +1 @@ +forge-std-1.15.0/=dependencies/forge-std-1.15.0/ diff --git a/counter/script/Counter.s.sol b/counter/script/Counter.s.sol new file mode 100644 index 0000000000000..cdc1fe9a1ba25 --- /dev/null +++ b/counter/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/counter/soldeer.lock b/counter/soldeer.lock new file mode 100644 index 0000000000000..af6c8601cd7ba --- /dev/null +++ b/counter/soldeer.lock @@ -0,0 +1,6 @@ +[[dependencies]] +name = "forge-std" +version = "1.15.0" +url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_15_0_27-02-2026_08:26:17_forge-std-1.15.zip" +checksum = "40d9b3b3d786eec4cd05fb9d818616015cbe7b8866643a9f0854495c938588c4" +integrity = "92accf4f7850eb9f5832f0ea77d633d36ebe993efc6d6c9f32369d31befc8a75" diff --git a/counter/src/Counter.sol b/counter/src/Counter.sol new file mode 100644 index 0000000000000..aded7997b0c35 --- /dev/null +++ b/counter/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/counter/test/Counter.t.sol b/counter/test/Counter.t.sol new file mode 100644 index 0000000000000..54b724f7ae766 --- /dev/null +++ b/counter/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/anvil/server/src/handler.rs b/crates/anvil/server/src/handler.rs index 250c486986240..95659d9eefbf6 100644 --- a/crates/anvil/server/src/handler.rs +++ b/crates/anvil/server/src/handler.rs @@ -49,7 +49,9 @@ pub async fn handle_request( Request::Single(call) => handle_call(call, handler).await.map(Response::Single), Request::Batch(calls) => { if calls.is_empty() { - return Some(Response::error(RpcError::invalid_request())); + return Some(Response::Batch(vec![anvil_rpc::response::RpcResponse::from( + RpcError::invalid_request(), + )])); } future::join_all(calls.into_iter().map(move |call| handle_call(call, handler.clone()))) .map(responses_as_batch) diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 23fda25c40faa..fc278cdfbc2a6 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -803,7 +803,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { } _ => SimpleCast::decode_raw_transaction::(&tx)?, }; - sh_println!("{}", serde_json::to_string_pretty(&decoded_tx)?)?; + sh_println!("{decoded_tx}")?; } CastSubcommand::RecoverAuthority { auth } => { let auth: SignedAuthorization = serde_json::from_str(&auth)?; diff --git a/crates/cast/src/cmd/miner.rs b/crates/cast/src/cmd/miner.rs index 346efb9a503a0..7768b0b58bf05 100644 --- a/crates/cast/src/cmd/miner.rs +++ b/crates/cast/src/cmd/miner.rs @@ -23,9 +23,9 @@ where handles.push(std::thread::spawn(move || { #[repr(C)] - struct B256Aligned(B256, [usize; 0]); - - let mut salt = B256Aligned(salt, []); + #[repr(C, align(8))] + struct B256Aligned(B256); + let mut salt = B256Aligned(salt); // SAFETY: `B256` is aligned to `usize`. let salt_word = unsafe { &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::() diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 86c6724a217c3..06a4afc24947c 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -3159,6 +3159,34 @@ Traces: "#]]); }); +// tests that displays a sample beacon block traces in Cancun +// https://github.com/foundry-rs/foundry/issues/12435 +casttest!(test_beacon_block_root_in_cancun, |prj, cmd| { + prj.clear(); + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "run", + "0xae290fe8c89c3e83dff20eeb2b8e3261bcdce0d66441c7056918dfb5fafe6d96", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [45054] 0xB731392c0EB5BF2092f9f7B520DA551f70Ea9131::Claim{value: 46698476594582387}() + ├─ [4320] 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02::00000000(00000000000000000000000000000000000000000000000069091d4b) [staticcall] + │ └─ ← [Return] 0x70c7855161ec07af782df915fb3e81702df40f34972da3d740cdfc132ac926f6 + ├─ emit NvStuck(param0: 0x6e6C36B970f8862bA3F148DEdAB8F98f5ed8b426, param1: 46698476594582387 [4.669e16], param2: 1762205003 [1.762e9]) + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + // tests that displays a sample contract artifact // casttest!(flaky_fetch_artifact_from_etherscan, |_prj, cmd| { diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index c98cfb69357bd..af1a09c38d109 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Cheatcodes", - "description": "Foundry cheatcodes. Learn more: ", + "description": "Foundry cheatcodes. Learn more: ", "type": "object", "properties": { "cheatcodes": { diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 29e968aeb5bc6..202b590769857 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -19,7 +19,7 @@ mod vm; pub use vm::Vm; // The `cheatcodes.json` schema. -/// Foundry cheatcodes. Learn more: +/// Foundry cheatcodes. Learn more: #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] @@ -178,7 +178,7 @@ interface Vm {{ eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo spec-cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index f2f11d91ad163..1436883c13aee 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1130,6 +1130,15 @@ mod tests { assert_eq!(latest.contractAddress, address!("20c0000000000000000000000000000000000000")); assert!(latest.success); - stdfs::remove_dir_all(root).unwrap(); + if root.exists() { + let root_canon = stdfs::canonicalize(&root).unwrap(); + let temp_canon = stdfs::canonicalize(env::temp_dir()).unwrap(); + assert!( + root_canon.starts_with(&temp_canon), + "refusing to remove non-temp test directory: {}", + root_canon.display() + ); + stdfs::remove_dir_all(&root).unwrap(); + } } } diff --git a/crates/cli/src/utils/suggestions.rs b/crates/cli/src/utils/suggestions.rs index a675ccae963c9..82a14a3b24beb 100644 --- a/crates/cli/src/utils/suggestions.rs +++ b/crates/cli/src/utils/suggestions.rs @@ -1,4 +1,5 @@ //! Helper functions for suggesting alternative values for a possibly erroneous user input. +use std::cmp::Ordering; /// Filters multiple strings from a given list of possible values which are similar /// to the passed in value `v` within a certain confidence by least confidence. diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index a66a0fef2fe0a..6921faabcb102 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,6 +17,7 @@ foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true +foundry-primitives.workspace = true alloy-chains.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 2087a85236154..e883810dcda34 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -18,7 +18,6 @@ use alloy_rpc_types::{ AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; -#[cfg(feature = "optimism")] use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec}; use revm::context_interface::transaction::SignedAuthorization; use serde::Deserialize; @@ -449,7 +448,6 @@ input {}", } } -#[cfg(feature = "optimism")] impl UIfmt for TxDeposit { fn pretty(&self) -> String { format!( @@ -474,7 +472,6 @@ input {}", } } -#[cfg(feature = "optimism")] impl UIfmt for TxPostExec { fn pretty(&self) -> String { format!( @@ -609,7 +606,6 @@ type {:#x} } } -#[cfg(feature = "optimism")] impl UIfmt for OpTxEnvelope { fn pretty(&self) -> String { match self { @@ -655,7 +651,6 @@ effectiveGasPrice {} } } -#[cfg(feature = "optimism")] impl UIfmt for op_alloy_rpc_types::Transaction { fn pretty(&self) -> String { format!( @@ -791,7 +786,6 @@ impl UIfmtSignatureExt for AnyTxEnvelope { } } -#[cfg(feature = "optimism")] impl UIfmtSignatureExt for OpTxEnvelope { fn signature_pretty(&self) -> Option<(String, String, String)> { self.signature().map(|sig| { @@ -1141,7 +1135,6 @@ mod tests { assert_eq!(b.pretty(), b32.pretty()); } - #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx() { let s = r#" @@ -1193,7 +1186,6 @@ yParity 1 ); } - #[cfg(feature = "optimism")] #[test] fn can_pretty_print_optimism_tx_through_any() { let s = r#" diff --git a/crates/common/src/tempo/keystore.rs b/crates/common/src/tempo/keystore.rs index b4f9527d1b106..eb1e5649092b1 100644 --- a/crates/common/src/tempo/keystore.rs +++ b/crates/common/src/tempo/keystore.rs @@ -5,8 +5,8 @@ use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; -use serde::{Deserialize, Serialize}; -use std::{env, fs, io::Write, path::PathBuf}; +use serde::Deserialize; +use std::path::{Component, Path, PathBuf}; /// Environment variable for an ephemeral Tempo private key. pub const TEMPO_PRIVATE_KEY_ENV: &str = "TEMPO_PRIVATE_KEY"; @@ -21,7 +21,7 @@ pub const DEFAULT_TEMPO_HOME: &str = ".tempo"; pub const WALLET_KEYS_PATH: &str = "wallet/keys.toml"; /// Wallet type matching `tempo-common`'s `WalletType` enum. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] #[serde(rename_all = "lowercase")] pub enum WalletType { #[default] @@ -30,7 +30,7 @@ pub enum WalletType { } /// Cryptographic key type. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)] #[serde(rename_all = "lowercase")] pub enum KeyType { #[default] @@ -40,7 +40,7 @@ pub enum KeyType { } /// Per-token spending limit stored in `keys.toml`. -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize)] pub struct StoredTokenLimit { pub currency: Address, pub limit: String, @@ -50,7 +50,7 @@ pub struct StoredTokenLimit { /// /// Mirrors the fields from `tempo-common::keys::model::KeyEntry`. /// Unknown fields are ignored by serde. -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize)] pub struct KeyEntry { /// Wallet type: "local" or "passkey". #[serde(default)] @@ -65,20 +65,20 @@ pub struct KeyEntry { #[serde(default)] pub key_type: KeyType, /// Key address (the EOA derived from the private key). - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub key_address: Option
, /// Key private key, stored inline in keys.toml. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub key: Option, /// RLP-encoded signed key authorization (hex string). /// Used in keychain mode to atomically provision the access key on-chain. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub key_authorization: Option, /// Expiry timestamp. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub expiry: Option, /// Per-token spending limits. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(default)] pub limits: Vec, } @@ -90,28 +90,32 @@ impl KeyEntry { } /// The top-level structure of `keys.toml`. -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize)] pub struct KeysFile { #[serde(default)] pub keys: Vec, } -/// Process-wide mutex used by tests that mutate `TEMPO_HOME`. -/// -/// Returns a [`tokio::sync::Mutex`] so async tests can hold it across `.await` -/// points without tripping `clippy::await_holding_lock`. -#[cfg(test)] -pub(crate) fn test_env_mutex() -> &'static tokio::sync::Mutex<()> { - static M: std::sync::OnceLock> = std::sync::OnceLock::new(); - M.get_or_init(|| tokio::sync::Mutex::new(())) -} - /// Resolve the Tempo home directory. /// /// Uses `TEMPO_HOME` env var if set, otherwise `~/.tempo`. +fn is_safe_tempo_home_override(path: &Path) -> bool { + !path + .components() + .any(|component| matches!(component, Component::ParentDir)) +} + pub fn tempo_home() -> Option { - if let Ok(home) = env::var(TEMPO_HOME_ENV) { - return Some(PathBuf::from(home)); + if let Ok(home) = std::env::var(TEMPO_HOME_ENV) { + let candidate = PathBuf::from(home); + if is_safe_tempo_home_override(&candidate) { + return Some(candidate); + } + tracing::warn!( + env = TEMPO_HOME_ENV, + ?candidate, + "ignoring unsafe TEMPO_HOME override containing parent directory components" + ); } dirs::home_dir().map(|h| h.join(DEFAULT_TEMPO_HOME)) } @@ -132,7 +136,7 @@ pub fn read_tempo_keys_file() -> Option { return None; } - let contents = match fs::read_to_string(&keys_path) { + let contents = match std::fs::read_to_string(&keys_path) { Ok(c) => c, Err(e) => { tracing::warn!(?keys_path, %e, "failed to read tempo keys file"); @@ -158,112 +162,3 @@ pub fn decode_key_authorization(hex_str: &str) -> eyre::Result let auth = T::decode(&mut bytes.as_slice())?; Ok(auth) } - -/// Atomically upsert a [`KeyEntry`] into `keys.toml`. -/// -/// Replaces any existing entry for the same `(wallet_address, chain_id)`. -/// Each Tempo wallet has at most one active access key per chain, so a fresh -/// login always supersedes the previous entry regardless of the new key -/// address. Creates the file (and parent directories) if missing. Writes via -/// temp file + rename so a crash mid-write cannot corrupt the file. -pub(crate) fn upsert_key_entry(entry: KeyEntry) -> eyre::Result<()> { - let path = tempo_keys_path().ok_or_else(|| eyre::eyre!("could not resolve tempo home"))?; - let dir = path.parent().ok_or_else(|| eyre::eyre!("invalid keys path: {}", path.display()))?; - fs::create_dir_all(dir)?; - - let mut file = read_tempo_keys_file().unwrap_or_default(); - file.keys - .retain(|k| !(k.wallet_address == entry.wallet_address && k.chain_id == entry.chain_id)); - file.keys.push(entry); - - let body = toml::to_string_pretty(&file)?; - let contents = format!( - "# Tempo wallet keys — managed by Foundry / Tempo CLI.\n# Do not edit manually.\n\n{body}" - ); - - let mut tmp = tempfile::NamedTempFile::new_in(dir)?; - tmp.write_all(contents.as_bytes())?; - tmp.flush()?; - tmp.persist(&path).map_err(|e| eyre::eyre!("failed to persist keys.toml: {e}"))?; - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str::FromStr; - - fn with_tempo_home(f: F) { - let tmp = tempfile::tempdir().unwrap(); - // SAFETY: process-global env access is serialized via the shared mutex. - let _g = test_env_mutex().blocking_lock(); - unsafe { std::env::set_var(TEMPO_HOME_ENV, tmp.path()) }; - f(); - unsafe { std::env::remove_var(TEMPO_HOME_ENV) }; - } - - #[test] - fn upsert_replaces_matching_entry_atomically() { - with_tempo_home(|| { - let wallet = Address::from_str("0x0000000000000000000000000000000000000001").unwrap(); - let key = Address::from_str("0x0000000000000000000000000000000000000abc").unwrap(); - - let mk = |expiry: u64| KeyEntry { - wallet_type: WalletType::Passkey, - wallet_address: wallet, - chain_id: 4217, - key_type: KeyType::Secp256k1, - key_address: Some(key), - key: Some("0xdead".to_string()), - key_authorization: Some("0xbeef".to_string()), - expiry: Some(expiry), - limits: vec![], - }; - - upsert_key_entry(mk(100)).unwrap(); - upsert_key_entry(mk(200)).unwrap(); - - let file = read_tempo_keys_file().unwrap(); - assert_eq!(file.keys.len(), 1); - assert_eq!(file.keys[0].expiry, Some(200)); - - // Different chain_id => separate entry. - let mut other = mk(300); - other.chain_id = 42431; - upsert_key_entry(other).unwrap(); - let file = read_tempo_keys_file().unwrap(); - assert_eq!(file.keys.len(), 2); - }); - } - - #[test] - fn upsert_replaces_when_key_address_changes() { - // Re-login produces a fresh random key address; the new entry must - // supersede the old one for the same (wallet, chain), not coexist. - with_tempo_home(|| { - let wallet = Address::from_str("0x0000000000000000000000000000000000000001").unwrap(); - let old_key = Address::from_str("0x000000000000000000000000000000000000aaaa").unwrap(); - let new_key = Address::from_str("0x000000000000000000000000000000000000bbbb").unwrap(); - - let mk = |key_addr: Address| KeyEntry { - wallet_type: WalletType::Passkey, - wallet_address: wallet, - chain_id: 4217, - key_type: KeyType::Secp256k1, - key_address: Some(key_addr), - key: Some("0xdead".to_string()), - key_authorization: Some("0xbeef".to_string()), - expiry: Some(100), - limits: vec![], - }; - - upsert_key_entry(mk(old_key)).unwrap(); - upsert_key_entry(mk(new_key)).unwrap(); - - let file = read_tempo_keys_file().unwrap(); - assert_eq!(file.keys.len(), 1, "old entry must be replaced, not duplicated"); - assert_eq!(file.keys[0].key_address, Some(new_key)); - }); - } -} diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 39c281e81be25..4c6978e0d9750 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -49,6 +49,9 @@ walkdir.workspace = true yansi.workspace = true clap = { version = "4", features = ["derive"] } +# schema +schemars = { version = "1.0", optional = true } + [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" @@ -60,3 +63,4 @@ tempfile.workspace = true [features] isolate-by-default = [] +schema = ["dep:schemars"] diff --git a/crates/config/assets/config.schema.json b/crates/config/assets/config.schema.json new file mode 100644 index 0000000000000..00a381eaf66ff --- /dev/null +++ b/crates/config/assets/config.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Config", + "description": "Foundry configuration. Learn more: ", + "type": "object" +} \ No newline at end of file diff --git a/crates/config/spec/Cargo.toml b/crates/config/spec/Cargo.toml new file mode 100644 index 0000000000000..89d40cf5606ce --- /dev/null +++ b/crates/config/spec/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "foundry-config-spec" +description = "Foundry configuration specification" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true +serde.workspace = true + +# schema +schemars = { version = "1.0", optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +schema = ["dep:schemars", "foundry-config/schema"] diff --git a/crates/config/spec/src/lib.rs b/crates/config/spec/src/lib.rs new file mode 100644 index 0000000000000..77a3bb7ab11c2 --- /dev/null +++ b/crates/config/spec/src/lib.rs @@ -0,0 +1,63 @@ +//! Config specification for Foundry. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use foundry_config::Config; +use serde::{Deserialize, Serialize}; + +// The `config.json` schema. +/// Foundry configuration. Learn more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct ConfigSchema { + #[serde(flatten)] + pub config: Config, +} + +#[cfg(test)] +#[expect(clippy::disallowed_macros)] +mod tests { + use super::*; + use std::{fs, path::Path}; + + #[cfg(feature = "schema")] + const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/config.schema.json"); + + /// Generates the configuration JSON schema. + #[cfg(feature = "schema")] + fn json_schema() -> String { + serde_json::to_string_pretty(&schemars::schema_for!(ConfigSchema)).unwrap() + } + + #[test] + #[cfg(feature = "schema")] + fn schema_up_to_date() { + ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); + } + + /// Checks that the `file` has the specified `contents`. If that is not the + /// case, updates the file and then fails the test. + fn ensure_file_contents(file: &Path, contents: &str) { + if fs::read_to_string(file).map(|old| normalize_newlines(&old) == normalize_newlines(contents)).unwrap_or(false) + { + // File is already up to date. + return; + } + + eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); + if std::env::var("CI").is_ok() { + eprintln!(" NOTE: run `cargo spec-config` locally and commit the updated files\n"); + } + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + fs::write(file, contents).unwrap(); + panic!("some file was not up to date and has been updated, simply re-run the tests"); + } + + fn normalize_newlines(s: &str) -> String { + s.replace("\r\n", "\n") + } +} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index b52e187262c14..a1f98a0b6acf0 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1320,13 +1320,32 @@ impl Config { } // Remove last test run failures file. - if let Err(err) = fs::remove_file(&self.test_failures_file) - && err.kind() != io::ErrorKind::NotFound - { - warnings.push(format!( - "failed to remove test failures file {}: {err}", - self.test_failures_file.display() - )); + let test_failures_path = if self.test_failures_file.is_absolute() { + self.test_failures_file.clone() + } else { + project.root().join(&self.test_failures_file) + }; + let root_canon = dunce::canonicalize(project.root()).unwrap_or_else(|_| project.root().to_path_buf()); + let validated_test_failures_path = test_failures_path + .parent() + .and_then(|parent| dunce::canonicalize(parent).ok().map(|p| (p, test_failures_path.file_name()))) + .and_then(|(parent, file_name)| file_name.map(|name| parent.join(name))); + + match validated_test_failures_path { + Some(path) if path.starts_with(&root_canon) => { + if let Err(err) = fs::remove_file(&path) && err.kind() != io::ErrorKind::NotFound { + warnings.push(format!( + "failed to remove test failures file {}: {err}", + path.display() + )); + } + } + _ => { + warnings.push(format!( + "skipped removing test failures file outside project root: {}", + test_failures_path.display() + )); + } } // Remove fuzz and invariant cache directories. diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 5dbf07c7a356c..dd5138f532074 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -28,6 +28,7 @@ foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true +alloy-network.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 667da6b442ca1..4e83eb168abca 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -62,6 +62,7 @@ alloy-primitives = { workspace = true, features = ["serde"] } alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-signer.workspace = true alloy-transport.workspace = true +alloy-hardforks.workspace = true tempo-alloy.workspace = true diff --git a/crates/forge/src/cmd/config.rs b/crates/forge/src/cmd/config.rs index 49716146c456b..fac66727c9d99 100644 --- a/crates/forge/src/cmd/config.rs +++ b/crates/forge/src/cmd/config.rs @@ -53,8 +53,8 @@ impl ConfigArgs { } else { config.to_string_pretty()? }; - sh_println!("{s}")?; + Ok(()) } } diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index dd8f3afd56197..938e9674c0cbe 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -1166,7 +1166,7 @@ fn merge_outcomes(base: &mut TestOutcome, other: TestOutcome) { let base_suite = e.get_mut(); base_suite.test_results.extend(other_suite.test_results); base_suite.warnings.extend(other_suite.warnings); - base_suite.duration = base_suite.duration.max(other_suite.duration); + base_suite.duration += other_suite.duration; } } } diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index c744ff6457596..223549b08b048 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -1374,7 +1374,6 @@ Compiling 21 files with [..] }); // Test preprocessed contracts with decode internal fns. -#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { prj.initialize_default_contracts(); prj.update_config(|config| { diff --git a/crates/lint/src/linter.rs b/crates/lint/src/linter.rs new file mode 100644 index 0000000000000..0865147167243 --- /dev/null +++ b/crates/lint/src/linter.rs @@ -0,0 +1,129 @@ +use foundry_compilers::Language; +use foundry_config::lint::Severity; +use solar_ast::{visit::Visit, Expr, ItemFunction, ItemStruct, VariableDefinition}; +use solar_interface::{ + data_structures::Never, + diagnostics::{DiagBuilder, DiagId, MultiSpan}, + Session, Span, +}; +use std::{ops::ControlFlow, path::PathBuf}; + +/// Trait representing a generic linter for analyzing and reporting issues in smart contract source +/// code files. A linter can be implemented for any smart contract language supported by Foundry. +/// +/// # Type Parameters +/// +/// - `Language`: Represents the target programming language. Must implement the [`Language`] trait. +/// - `Lint`: Represents the types of lints performed by the linter. Must implement the [`Lint`] +/// trait. +/// +/// # Required Methods +/// +/// - `lint`: Scans the provided source files emitting a diagnostic for lints found. +pub trait Linter: Send + Sync + Clone { + type Language: Language; + type Lint: Lint; + + fn lint(&self, input: &[PathBuf]); +} + +pub trait Lint { + fn id(&self) -> &'static str; + fn severity(&self) -> Severity; + fn description(&self) -> &'static str; + fn help(&self) -> &'static str; +} + +pub struct LintContext<'s> { + sess: &'s Session, + desc: bool, +} + +impl<'s> LintContext<'s> { + pub fn new(sess: &'s Session, with_description: bool) -> Self { + Self { sess, desc: with_description } + } + + // Helper method to emit diagnostics easily from passes + pub fn emit(&self, lint: &'static L, span: Span) { + let desc = if self.desc { lint.description() } else { "" }; + let diag: DiagBuilder<'_, ()> = self + .sess + .dcx + .diag(lint.severity().into(), desc) + .code(DiagId::new_str(lint.id())) + .span(MultiSpan::from_span(span)) + .help(lint.help()); + + diag.emit(); + } +} + +/// Trait for lints that operate directly on the AST. +/// Its methods mirror `solar_ast::visit::Visit`, with the addition of `LintContext`. +pub trait EarlyLintPass<'ast>: Send + Sync { + fn check_expr(&mut self, _ctx: &LintContext<'_>, _expr: &'ast Expr<'ast>) {} + fn check_item_struct(&mut self, _ctx: &LintContext<'_>, _struct: &'ast ItemStruct<'ast>) {} + fn check_item_function(&mut self, _ctx: &LintContext<'_>, _func: &'ast ItemFunction<'ast>) {} + fn check_variable_definition( + &mut self, + _ctx: &LintContext<'_>, + _var: &'ast VariableDefinition<'ast>, + ) { + } + + // TODO: Add methods for each required AST node type +} + +/// Visitor struct for `EarlyLintPass`es +pub struct EarlyLintVisitor<'a, 's, 'ast> { + pub ctx: &'a LintContext<'s>, + pub passes: &'a mut [Box + 's>], +} + +impl<'s, 'ast> Visit<'ast> for EarlyLintVisitor<'_, 's, 'ast> +where + 's: 'ast, +{ + type BreakValue = Never; + + fn visit_expr(&mut self, expr: &'ast Expr<'ast>) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_expr(self.ctx, expr) + } + self.walk_expr(expr) + } + + fn visit_variable_definition( + &mut self, + var: &'ast VariableDefinition<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_variable_definition(self.ctx, var) + } + self.walk_variable_definition(var) + } + + fn visit_item_struct( + &mut self, + strukt: &'ast ItemStruct<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_struct(self.ctx, strukt) + } + self.walk_item_struct(strukt) + } + + fn visit_item_function( + &mut self, + func: &'ast ItemFunction<'ast>, + ) -> ControlFlow { + for pass in self.passes.iter_mut() { + pass.check_item_function(self.ctx, func) + } + self.walk_item_function(func) + } + + // TODO: Add methods for each required AST node type, mirroring `solar_ast::visit::Visit` method + // sigs + adding `LintContext` +} diff --git a/crates/lint/src/sol/gas/keccak.rs b/crates/lint/src/sol/gas/keccak.rs index b6a18f292a91b..cb942510bbb49 100644 --- a/crates/lint/src/sol/gas/keccak.rs +++ b/crates/lint/src/sol/gas/keccak.rs @@ -91,7 +91,7 @@ fn extract_keccak256_arg<'hir>(expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir: return None; }; - (is_keccak && args.len() == 1).then(|| &args[0]) + if is_keccak && args.len() == 1 { Some(&args[0]) } else { None } } // -- HELPER FUNCTIONS AND STRUCTS ---------------------------------------------------------------- diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index b667a884b79de..a0fd49f3238f6 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -56,6 +56,7 @@ alloy-primitives.workspace = true alloy-eips.workspace = true alloy-consensus.workspace = true thiserror.workspace = true +tempo-alloy.workspace = true tempo-alloy.workspace = true tempo-primitives.workspace = true diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 5c5155aa1678b..4ee9c3fb53cb6 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -71,7 +71,7 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); - if to.is_some() { + if let Some(alloy_primitives::TxKind::Call(_)) = to { builder.set_call( &address_to_abi, &self.execution_artifacts.decoder, @@ -129,7 +129,7 @@ impl PreSimulationState { let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); let tx = transaction.tx_mut(); - let to = tx.to(); + let to = if let Some(alloy_primitives::TxKind::Call(to)) = tx.to() { Some(to) } else { None }; let result = runner .simulate( tx.from() diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 06f83886d0fd2..f0f8ef94ed978 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -123,6 +123,18 @@ impl ScriptTester { for entry in fs::read_dir(&from_dir)? { let file = &entry?.path(); let name = file.file_name().unwrap(); + // Validate file name to avoid path traversal and absolute paths + let name_str = name.to_string_lossy(); + if name_str.contains("..") || name_str.contains("/") || name_str.contains("\\") { + // Skip invalid (potentially dangerous) file names + continue; + } + // Optionally verify canonicalized file is in from_dir to avoid symlink traversal + if let Ok(canonical_file) = file.canonicalize() { + if !canonical_file.starts_with(from_dir.canonicalize().unwrap_or_else(|_| from_dir.clone())) { + continue; + } + } fs::copy(file, to_dir.join(name))?; } Ok(()) diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 48489e43e34d9..fa065425b5281 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -3,12 +3,28 @@ use foundry_config::Config; use std::{ env, fs::{self, File}, - io::{Read, Seek, Write}, + io::{self, IsTerminal, Read, Seek, Write}, path::{Path, PathBuf}, process::Command, sync::LazyLock, }; +/// Base directory under which all test utility filesystem operations are constrained. +/// Using a fixed directory under the system temp dir avoids trusting the current +/// working directory (which may be user-controlled) as a security boundary. +static TEST_UTIL_BASE: LazyLock = LazyLock::new(|| { + // Resolve the system temp directory to an absolute, canonical path where possible. + // If canonicalization fails for any reason, fall back to the raw temp_dir value. + let tmp = env::temp_dir(); + let mut base = tmp + .canonicalize() + .unwrap_or(tmp); + base.push("foundry_test_utils"); + // Ignore errors here; they will surface when the path is actually used. + let _ = fs::create_dir_all(&base); + base +}); + /// Directories to skip when copying project directories. /// These are build artifacts and runtime-generated files that should not be copied to temp /// workspaces. @@ -19,6 +35,9 @@ pub use crate::{ext::*, prj::*}; /// The commit of forge-std to use. pub const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev"); +/// Stores whether `stdout` is a tty / terminal. +pub static IS_TTY: LazyLock = LazyLock::new(|| std::io::stdout().is_terminal()); + /// Global default template path. Contains the global template project from which all other /// temp projects are initialized. See [`initialize()`] for more info. static TEMPLATE_PATH: LazyLock = @@ -146,7 +165,9 @@ pub fn get_compiled(project: &mut Project) -> ProjectCompileOutput { out = project.compile().unwrap(); test_debug!("compiled {}", lock_file_path.display()); - assert!(!out.has_compiler_errors(), "Compiled with errors:\n{out}"); + if out.has_compiler_errors() { + panic!("Compiled with errors:\n{out}"); + } if let Some(write) = &mut write { write.write_all(crate::fd_lock::LOCK_TOKEN).unwrap(); @@ -174,7 +195,7 @@ pub fn get_vyper() -> Vyper { let path = VYPER.as_path(); let mut file = File::create(path).unwrap(); if let Err(e) = file.try_lock() { - if matches!(e, fs::TryLockError::WouldBlock) { + if let fs::TryLockError::WouldBlock = e { file.lock().unwrap(); assert!(path.exists()); return Vyper::new(path).unwrap(); @@ -230,22 +251,77 @@ pub fn read_string(path: impl AsRef) -> String { /// like `out/`, `cache/`, and `broadcast/` which are build artifacts that should not be /// copied to temporary test workspaces. pub fn copy_dir_filtered(src: &Path, dst: &Path) -> std::io::Result<()> { - fs::create_dir_all(dst)?; - copy_dir_filtered_inner(src, dst, true) + let src = resolve_and_validate_under_base(src)?; + let dst = resolve_and_validate_under_base(dst)?; + + fs::create_dir_all(&dst)?; + copy_dir_filtered_inner(&src, &dst, true) +} + +/// Resolve a path against a safe base directory and ensure it does not escape that base. +/// +/// This guards against using uncontrolled paths that could traverse outside the intended +/// workspace (for example, via `..` components or absolute paths). +fn resolve_and_validate_under_base(path: &Path) -> io::Result { + // Use a fixed base directory for test utilities instead of the current working + // directory, which may be influenced by the environment. + let base = TEST_UTIL_BASE.clone(); + + // If `path` is absolute, interpret it relative to the base by stripping the + // root and joining the remaining components. This avoids treating arbitrary + // absolute paths as trustworthy. + let joined = if path.is_absolute() { + let relative_components = path.components().filter_map(|c| { + use std::path::Component; + match c { + Component::Normal(p) => Some(PathBuf::from(p)), + // Skip root and current-dir components; preserve parent-dir so that + // canonicalization below can detect and resolve them safely. + Component::RootDir | Component::CurDir => None, + Component::ParentDir => Some(PathBuf::from("..")), + Component::Prefix(_) => None, + } + }); + let mut rel = PathBuf::new(); + for c in relative_components { + rel.push(c); + } + base.join(rel) + } else { + base.join(path) + }; + + let canonical = joined.canonicalize()?; + if !canonical.starts_with(&base) { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "path escapes allowed base directory", + )); + } + + Ok(canonical) } fn copy_dir_filtered_inner(src: &Path, dst: &Path, is_root: bool) -> std::io::Result<()> { - for entry in fs::read_dir(src)? { + // Ensure that each recursion step operates on paths that are constrained to the + // configured base directory. This guarantees that any `src_path` passed to + // filesystem operations cannot escape the allowed workspace even if the initial + // input was influenced by the user. + let src = resolve_and_validate_under_base(src)?; + let dst = resolve_and_validate_under_base(dst)?; + + for entry in fs::read_dir(&src)? { let entry = entry?; let ty = entry.file_type()?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); + let name = entry.file_name(); + let src_path = src.join(&name); + let dst_path = dst.join(&name); if ty.is_dir() { // Skip build artifact directories at the root level if is_root - && let Some(name) = entry.file_name().to_str() - && SKIP_DIRS.contains(&name) + && let Some(name_str) = name.to_str() + && SKIP_DIRS.contains(&name_str) { continue; } diff --git a/crates/wallets/src/tempo.rs b/crates/wallets/src/tempo.rs new file mode 100644 index 0000000000000..a86b568fdea2b --- /dev/null +++ b/crates/wallets/src/tempo.rs @@ -0,0 +1,196 @@ +use alloy_eips::Encodable2718; +use alloy_primitives::{Address, hex}; +use alloy_rlp::Decodable; +use alloy_signer::Signer; +use eyre::Result; +use std::path::PathBuf; +use tempo_alloy::rpc::TempoTransactionRequest; +use tempo_primitives::transaction::{ + KeychainSignature, PrimitiveSignature, SignedKeyAuthorization, TempoSignature, +}; + +use crate::{WalletSigner, utils}; + +/// Wallet type: how this wallet was created. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum WalletType { + #[default] + Local, + Passkey, +} + +/// Cryptographic key type. +#[derive(Clone, Copy, Default, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +enum KeyType { + #[default] + Secp256k1, + P256, + WebAuthn, +} + +/// A single entry from Tempo's `keys.toml`. +#[derive(serde::Deserialize)] +#[allow(dead_code)] +struct KeyEntry { + #[serde(default)] + wallet_type: WalletType, + #[serde(default)] + wallet_address: Address, + #[serde(default)] + chain_id: u64, + #[serde(default)] + key_type: KeyType, + #[serde(default)] + key_address: Option
, + #[serde(default)] + key: Option, + #[serde(default)] + key_authorization: Option, + #[serde(default)] + expiry: Option, + #[serde(default)] + limits: Vec, +} + +/// Per-token spending limit stored in `keys.toml`. +#[derive(serde::Deserialize)] +struct StoredTokenLimit { + #[allow(dead_code)] + currency: Address, + #[allow(dead_code)] + limit: String, +} + +/// The top-level structure of `~/.tempo/wallet/keys.toml`. +#[derive(serde::Deserialize)] +struct KeysFile { + #[serde(default)] + keys: Vec, +} + +/// Configuration for a Tempo access key (keychain mode). +/// +/// When a Tempo wallet entry uses keychain mode (`wallet_address != key_address`), the signer +/// is an access key that signs on behalf of the root wallet. This struct carries the metadata +/// needed to construct the correct transaction. +#[derive(Debug, Clone)] +pub struct TempoAccessKeyConfig { + /// The root wallet address (the `from` address for transactions). + pub wallet_address: Address, + /// The access key's address (derived from the private key that actually signs). + pub key_address: Address, + /// Decoded key authorization for on-chain provisioning. + /// + /// When present, callers should check whether the key is already provisioned on-chain + /// (via the AccountKeychain precompile) before including this in a transaction. + pub key_authorization: Option, +} + +/// Result of looking up an address in Tempo's key store. +pub enum TempoLookup { + /// A direct (EOA) signer was found — `wallet_address == key_address`. + Direct(WalletSigner), + /// A keychain (access key) signer was found — `wallet_address != key_address`. + Keychain(WalletSigner, Box), + /// No matching entry was found. + NotFound, +} + +/// Returns the path to Tempo's keys file. +/// +/// Respects `TEMPO_HOME` env var, defaulting to `~/.tempo`. +fn keys_path() -> Option { + let base = std::env::var_os("TEMPO_HOME") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|h| h.join(".tempo")))?; + Some(base.join("wallet").join("keys.toml")) +} + +/// Decodes a hex-encoded, RLP-encoded [`SignedKeyAuthorization`]. +fn decode_key_authorization(hex_str: &str) -> Result { + let bytes = hex::decode(hex_str)?; + let auth = SignedKeyAuthorization::decode(&mut bytes.as_slice())?; + Ok(auth) +} + +/// Looks up a signer for the given address in Tempo's `keys.toml`. +/// +/// Returns [`TempoLookup::Direct`] if a direct-mode (EOA) key is found, +/// [`TempoLookup::Keychain`] if a keychain-mode access key is found, +/// or [`TempoLookup::NotFound`] if no entry matches. +pub fn lookup_signer(from: Address) -> Result { + let path = match keys_path() { + Some(p) if p.is_file() => p, + _ => return Ok(TempoLookup::NotFound), + }; + + let contents = std::fs::read_to_string(&path)?; + let file: KeysFile = toml::from_str(&contents)?; + + for entry in &file.keys { + if entry.wallet_address != from { + continue; + } + + let Some(key) = &entry.key else { + continue; + }; + + // Direct mode: wallet_address == key_address (or key_address is absent). + let is_direct = + entry.key_address.is_none() || entry.key_address == Some(entry.wallet_address); + + let signer = utils::create_private_key_signer(key)?; + + if is_direct { + return Ok(TempoLookup::Direct(signer)); + } + + // Keychain mode: the key is an access key signing on behalf of wallet_address. + let key_authorization = + entry.key_authorization.as_deref().map(decode_key_authorization).transpose()?; + + let config = TempoAccessKeyConfig { + wallet_address: entry.wallet_address, + // SAFETY: `is_direct` was false, so `key_address` is `Some` and != wallet_address + key_address: entry.key_address.unwrap(), + key_authorization, + }; + return Ok(TempoLookup::Keychain(signer, Box::new(config))); + } + + Ok(TempoLookup::NotFound) +} + +/// Signs a Tempo transaction request using an access key (keychain V2 mode). +/// +/// Bypasses the standard `EthereumWallet` signing path and instead: +/// 1. Builds the `TempoTransaction` from the request +/// 2. Computes the V2 keychain signing hash +/// 3. Signs with the access key +/// 4. Wraps in a `KeychainSignature` and encodes to EIP-2718 wire format +pub async fn sign_with_access_key( + tx_request: impl Into, + signer: &impl Signer, + wallet_address: Address, +) -> Result> { + let tx_request: TempoTransactionRequest = tx_request.into(); + let tempo_tx = tx_request + .build_aa() + .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; + + let sig_hash = tempo_tx.signature_hash(); + let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); + let raw_sig = signer.sign_hash(&signing_hash).await?; + + let keychain_sig = + KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); + let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); + + let mut buf = Vec::new(); + aa_signed.encode_2718(&mut buf); + + Ok(buf) +} diff --git a/deny.toml b/deny.toml index 1a0e1e8e53005..0f891df4bfbfe 100644 --- a/deny.toml +++ b/deny.toml @@ -100,7 +100,10 @@ unknown-git = "deny" allow-git = [ "https://github.com/alloy-rs/alloy", "https://github.com/alloy-rs/evm", + "https://github.com/foundry-rs/compilers", + "https://github.com/foundry-rs/foundry-fork-db", "https://github.com/foundry-rs/foundry-core", + "https://github.com/foundry-rs/optimism", "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/paradigmxyz/solar", "https://github.com/bluealloy/revm", @@ -111,7 +114,5 @@ allow-git = [ "https://github.com/tempoxyz/mpp-rs", # Transitive dependency of Tempo "https://github.com/paradigmxyz/reth", - "https://github.com/paradigmxyz/reth-core", - # Temporary: upstream OP crates until release is published. - "https://github.com/ethereum-optimism/optimism", + "https://github.com/stevencartavia/reth", ] diff --git a/doc/doc-filelist.js b/doc/doc-filelist.js new file mode 100644 index 0000000000000..c2a398ff94c23 --- /dev/null +++ b/doc/doc-filelist.js @@ -0,0 +1 @@ +var tree={}; \ No newline at end of file diff --git a/doc/doc-script.js b/doc/doc-script.js new file mode 100644 index 0000000000000..7fa122605e7cb --- /dev/null +++ b/doc/doc-script.js @@ -0,0 +1,228 @@ +// # res/script.js +// +// This is the script file that gets copied into the output. It mainly manages the display +// of the folder tree. The idea of this script file is to be minimal and standalone. So +// that means no jQuery. + +// Use localStorage to store data about the tree's state: whether or not +// the tree is visible and which directories are expanded. Unless the state +var sidebarVisible = (window.localStorage && window.localStorage.docker_showSidebar) ? + window.localStorage.docker_showSidebar == 'yes' : + defaultSidebar; + +/** + * ## makeTree + * + * Consructs the folder tree view + * + * @param {object} treeData Folder structure as in [queueFile](../src/docker.js.html#docker.prototype.queuefile) + * @param {string} root Path from current file to root (ie `'../../'` etc.) + * @param {string} filename The current file name + */ +function makeTree(treeData, root, filename) { + var treeNode = document.getElementById('tree'); + var treeHandle = document.getElementById('sidebar-toggle'); + treeHandle.addEventListener('click', toggleTree, false); + + // Build the html and add it to the container. + treeNode.innerHTML = nodeHtml('', treeData, '', root); + + // Root folder (whole tree) should always be open + treeNode.childNodes[0].className += ' open'; + + // Attach click event handler + treeNode.addEventListener('click', nodeClicked, false); + + if (sidebarVisible) document.body.className += ' sidebar'; + + // Restore scroll position from localStorage if set. And attach scroll handler + if (window.localStorage && window.localStorage.docker_treeScroll) treeNode.scrollTop = window.localStorage.docker_treeScroll; + treeNode.onscroll = treeScrolled; + + // Only set a class to allow CSS transitions after the tree state has been painted + setTimeout(function() { document.body.className += ' slidey'; }, 100); +} + +/** + * ## treeScrolled + * + * Called when the tree is scrolled. Stores the scroll position in localStorage + * so it can be restored on the next pageview. + */ +function treeScrolled() { + var tree = document.getElementById('tree'); + if (window.localStorage) window.localStorage.docker_treeScroll = tree.scrollTop; +} + +/** + * ## nodeClicked + * + * Called when a directory is clicked. Toggles open state of the directory + * + * @param {Event} e The click event + */ +function nodeClicked(e) { + // Find the target + var t = e.target; + + // If the click target is actually a file (rather than a directory), ignore it + if (t.tagName.toLowerCase() !== 'div' || t.className === 'children') return; + + // Recurse upwards until we find the actual directory node + while (t && t.className.substring(0, 3) != 'dir') t = t.parentNode; + + // If we're at the root node, then do nothing (we don't allow collapsing of the whole tree) + if (!t || t.parentNode.id == 'tree') return; + + // Find the path and toggle the state, saving the state in the localStorage variable + var path = t.getAttribute('rel'); + if (t.className.indexOf('open') !== -1) { + t.className = t.className.replace(/\s*open/g, ''); + if (window.localStorage) window.localStorage.removeItem('docker_openPath:' + path); + } else { + t.className += ' open'; + if (window.localStorage) window.localStorage['docker_openPath:' + path] = 'yes'; + } +} + + +/** + * ## nodeHtml + * + * Constructs the markup for a directory in the tree + * + * @param {string} nodename The node name. + * @param {object} node Node object of same format as whole tree. + * @param {string} path The path form the base to this node + * @param {string} root Relative path from current page to root + */ +function nodeHtml(nodename, node, path, root) { + // Firstly, figure out whether or not the directory is expanded from localStorage + var isOpen = window.localStorage && window.localStorage['docker_openPath:' + path] == 'yes'; + var out = '
'; + out += '
' + nodename + '
'; + out += '
'; + + // Loop through all child directories first + if (node.dirs) { + var dirs = []; + for (var i in node.dirs) { + if (node.dirs.hasOwnProperty(i)) dirs.push({ name: i, html: nodeHtml(i, node.dirs[i], path + i + '/', root) }); + } + // Have to store them in an array first and then sort them alphabetically here + dirs.sort(function(a, b) { return (a.name > b.name) ? 1 : (a.name == b.name) ? 0 : -1; }); + + for (var k = 0; k < dirs.length; k += 1) out += dirs[k].html; + } + + // Now loop through all the child files alphabetically + if (node.files) { + node.files.sort(); + for (var j = 0; j < node.files.length; j += 1) { + out += '' + node.files[j] + ''; + } + } + + // Close things off + out += '
'; + + return out; +} + +/** + * ## toggleTree + * + * Toggles the visibility of the folder tree + */ +function toggleTree() { + // Do the actual toggling by modifying the class on the body element. That way we can get some nice CSS transitions going. + if (sidebarVisible) { + document.body.className = document.body.className.replace(/\s*sidebar/g, ''); + sidebarVisible = false; + } else { + document.body.className += ' sidebar'; + sidebarVisible = true; + } + if (window.localStorage) { + if (sidebarVisible) { + window.localStorage.docker_showSidebar = 'yes'; + } else { + window.localStorage.docker_showSidebar = 'no'; + } + } +} + +/** + * ## wireUpTabs + * + * Wires up events on the sidebar tabe + */ +function wireUpTabs() { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Each tab has a class corresponding of the id of its tab pane + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + children[i].addEventListener('click', function(c) { + return function() { switchTab(c); }; + }(children[i].className)); + } +} + +/** + * ## switchTab + * + * Switches tabs in the sidebar + * + * @param {string} tab The ID of the tab to switch to + */ +function switchTab(tab) { + var tabEl = document.getElementById('sidebar_switch'); + var children = tabEl.childNodes; + + // Easiest way to go through tabs without any kind of selector is just to look at the tab bar + for (var i = 0, l = children.length; i < l; i += 1) { + // Ignore text nodes + if (children[i].nodeType !== 1) continue; + + // Figure out what tab pane this tab button corresponts to + var t = children[i].className.replace(/\s.*$/, ''); + if (t === tab) { + // Show the tab pane, select the tab button + document.getElementById(t).style.display = 'block'; + if (children[i].className.indexOf('selected') === -1) children[i].className += ' selected'; + } else { + // Hide the tab pane, deselect the tab button + document.getElementById(t).style.display = 'none'; + children[i].className = children[i].className.replace(/\sselected/, ''); + } + } + + // Store the last open tab in localStorage + if (window.localStorage) window.localStorage.docker_sidebarTab = tab; +} + +/** + * ## window.onload + * + * When the document is ready, make the sidebar and all that jazz + */ +(function(init) { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', init); + } else { // IE8 and below + window.onload = init; + } +}(function() { + makeTree(tree, relativeDir, thisFile); + wireUpTabs(); + + // Switch to the last viewed sidebar tab if stored, otherwise default to folder tree + if (window.localStorage && window.localStorage.docker_sidebarTab) { + switchTab(window.localStorage.docker_sidebarTab); + } else { + switchTab('tree'); + } +})); diff --git a/doc/doc-style.css b/doc/doc-style.css new file mode 100644 index 0000000000000..2019a1b7659c6 --- /dev/null +++ b/doc/doc-style.css @@ -0,0 +1,403 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +/* Base color: saturation 0; */ +.hljs, +.hljs-subst { + color: #444; +} +.hljs-comment { + color: #888888; +} +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} +/* User color: hue: 0 */ +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #BC6060; +} +/* Language color: hue: 90; */ +.hljs-literal { + color: #78A960; +} +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} +/* Meta color: hue: 200 */ +.hljs-meta { + color: #1f7199; +} +.hljs-meta-string { + color: #4d99bf; +} +/* Misc effects */ +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + margin: 0; + padding: 0; + background: #ffffff; + color: #4d4d4d; +} +p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 15px 0; +} +h1 { + margin-top: 40px; +} +a { + color: #880000; +} +a:visited { + color: #880000; +} +#tree, +#headings { + position: absolute; + top: 30px; + left: 0; + bottom: 0; + width: 290px; + padding: 10px 0; + overflow: auto; +} +#sidebar_wrapper { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 0; + overflow: hidden; + background: #e7e7e7; +} +#sidebar_switch { + position: absolute; + top: 0; + left: 0; + width: 290px; + height: 29px; + border-bottom: 1px solid; + background: #e2e2e2; + border-bottom-color: #d6d6d6; +} +#sidebar_switch span { + display: block; + float: left; + width: 50%; + text-align: center; + line-height: 29px; + cursor: pointer; + color: #4b4b4b; +} +#sidebar_switch span:hover { + background: #e7e7e7; +} +#sidebar_switch .selected { + font-weight: bold; + background: #ededed; + color: #444; +} +.slidey #sidebar_wrapper { + -webkit-transition: width 250ms linear; + -moz-transition: width 250ms linear; + -ms-transition: width 250ms linear; + -o-transition: width 250ms linear; + transition: width 250ms linear; +} +.sidebar #sidebar_wrapper { + width: 290px; +} +#tree .nodename { + text-indent: 12px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAg0lEQVQYlWNIS0tbAcSK////Z8CHGTIzM7+mp6d/ASouwqswKyvrO1DRfyg+CcRaxCgE4Z9A3AjEbIQUgjHQOQvwKgS6+ffChQt3AiUDcCqsra29d/v27R6ghCVWN2ZnZ/9YuXLlRqBAPBALYvVMR0fHmQcPHrQBOUZ4gwfqFj5CAQ4Al6wLIYDwo9QAAAAASUVORK5CYII="); + background-repeat: no-repeat; + background-position: left center; + cursor: pointer; +} +#tree .open > .nodename { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAlElEQVQYlWNIS0tbCsT/8eCN////Z2B49OhRfHZ29jdsioDiP27evJkNVggkONeuXbscm8Jly5atA8rzwRSCsG5DQ8MtZEU1NTUPgOLGUHm4QgaQFVlZWT9BijIzM39fuHChDCaHohBkBdCq9SCF8+bN2wHkC+FSCMLGkyZNOvb9+3dbNHEMhSDsDsRMxCjEiolWCADeUBHgU/IGQQAAAABJRU5ErkJggg=="); + background-position: left 7px; +} +#tree .dir, +#tree .file { + position: relative; + min-height: 20px; + line-height: 20px; + padding-left: 12px; +} +#tree .dir > .children, +#tree .file > .children { + display: none; +} +#tree .dir.open > .children, +#tree .file.open > .children { + display: block; +} +#tree .file { + padding-left: 24px; + display: block; + text-decoration: none; + color: #444; +} +#tree > .dir { + padding-left: 0; +} +#headings .heading a { + text-decoration: none; + padding-left: 10px; + display: block; + color: #444; +} +#headings .h1 { + padding-left: 0; + margin-top: 10px; + font-size: 1.3em; +} +#headings .h2 { + padding-left: 10px; + margin-top: 8px; + font-size: 1.1em; +} +#headings .h3 { + padding-left: 20px; + margin-top: 5px; + font-size: 1em; +} +#headings .h4 { + padding-left: 30px; + margin-top: 3px; + font-size: 0.9em; +} +#headings .h5 { + padding-left: 40px; + margin-top: 1px; + font-size: 0.8em; +} +#headings .h6 { + padding-left: 50px; + font-size: 0.75em; +} +#sidebar-toggle { + position: fixed; + top: 0; + left: 0; + width: 5px; + bottom: 0; + z-index: 2; + cursor: pointer; + background: #dfdfdf; +} +#sidebar-toggle:hover { + width: 10px; + background: #d6d6d6; +} +.slidey #sidebar-toggle, +.slidey #container { + -webkit-transition: all 250ms linear; + -moz-transition: all 250ms linear; + -ms-transition: all 250ms linear; + -o-transition: all 250ms linear; + transition: all 250ms linear; +} +.sidebar #sidebar-toggle { + left: 290px; +} +#container { + position: fixed; + left: 5px; + right: 0; + top: 0; + bottom: 0; + overflow: auto; +} +.sidebar #container { + left: 295px; +} +.no-sidebar #sidebar_wrapper, +.no-sidebar #sidebar-toggle { + display: none; +} +.no-sidebar #container { + left: 0; +} +#page { + padding-top: 40px; +} +table td { + border: 0; + outline: 0; +} +.docs.markdown { + padding: 10px 50px; +} +td.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; +} +.docs pre { + margin: 15px 0 15px; + padding: 5px; + padding-left: 10px; + border: 1px solid #d6d6d6; + background: #F0F0F0; + font-size: 12px; + overflow: auto; +} +.docs pre.code_stats { + font-size: 60%; +} +.docs p tt, +.docs li tt, +.docs p code, +.docs li code { + border: 1px solid #d6d6d6; + font-size: 12px; + padding: 0 0.2em; + background: #e7e7e7; +} +.dox { + border-top: 1px solid #dddddd; + padding-top: 10px; + padding-bottom: 10px; +} +.dox .details { + padding: 10px; + background: #F0F0F0; + border: 1px solid #d6d6d6; + margin-bottom: 10px; +} +.dox .dox_tag_title { + font-weight: bold; +} +.dox .dox_tag_detail { + margin-left: 10px; +} +.dox .dox_tag_detail span { + margin-right: 5px; +} +.dox .dox_type { + font-style: italic; +} +.dox .dox_tag_name { + font-weight: bold; +} +.pilwrap { + position: relative; + padding-top: 1px; +} +.pilwrap .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + color: #555555; +} +.pilwrap .pilcrow:before { + content: '\b6'; +} +.pilwrap:hover .pilcrow { + opacity: 1; +} +td.code { + padding: 8px 15px 8px 25px; + width: 100%; + vertical-align: top; + border-left: 1px solid #d6d6d6; + background: #F0F0F0; +} +.background { + border-left: 1px solid #d6d6d6; + position: absolute; + z-index: -1; + top: 0; + right: 0; + bottom: 0; + left: 525px; + background: #F0F0F0; +} +pre, +tt, +code { + font-size: 12px; + line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; + padding: 0; + white-space: pre-wrap; + background: #F0F0F0; +} +.line-num { + display: inline-block; + width: 50px; + text-align: right; + opacity: 0.3; + margin-left: -20px; + text-decoration: none; + color: #888888; +} +.line-num:before { + content: attr(data-line); +} diff --git a/sleep.json b/sleep.json new file mode 100644 index 0000000000000..5b430e1e663f6 --- /dev/null +++ b/sleep.json @@ -0,0 +1,955 @@ +{ + "results": [ + { + "command": "sleep 0.020", + "mean": 0.023726515413333333, + "stddev": 0.004602014051751124, + "median": 0.02267755758, + "user": 0.0013185473333333334, + "system": 0.0020899164444444446, + "min": 0.02109890308, + "max": 0.05602819808, + "times": [ + 0.02856005608, + 0.02346135008, + 0.02202502208, + 0.02139558708, + 0.02265920408, + 0.02121691608, + 0.02272505608, + 0.02114247908, + 0.02157142808, + 0.021514666079999998, + 0.02161920108, + 0.02335035008, + 0.02224331408, + 0.02228639708, + 0.02152537208, + 0.021732302079999998, + 0.02273370308, + 0.02115513608, + 0.02268494308, + 0.02244547308, + 0.023943647079999998, + 0.02324528508, + 0.02152617908, + 0.023991903079999998, + 0.02250884108, + 0.02342551708, + 0.02113216608, + 0.02168223108, + 0.02222267508, + 0.02273532108, + 0.02273995308, + 0.05602819808, + 0.02501500608, + 0.03121396008, + 0.02424400108, + 0.02459129108, + 0.02633760708, + 0.02377406808, + 0.02365474708, + 0.02406064008, + 0.02300910408, + 0.02437339208, + 0.02317403908, + 0.02257532008, + 0.02267017208, + 0.02356714508, + 0.02367204808, + 0.02258227108, + 0.02330384008, + 0.02225645108, + 0.02478414908, + 0.02484724308, + 0.02270765708, + 0.02339114708, + 0.02450795908, + 0.02348840008, + 0.044674490080000004, + 0.028041754080000002, + 0.022940745079999998, + 0.02259975308, + 0.022112378079999998, + 0.02271348408, + 0.02320266708, + 0.02284982108, + 0.02244050908, + 0.02238655808, + 0.022084648079999998, + 0.02241669808, + 0.02523103408, + 0.02256237908, + 0.03532525108, + 0.02232798408, + 0.02173793008, + 0.021903001079999998, + 0.02288046308, + 0.02368652508, + 0.02211418708, + 0.02265551308, + 0.02187778308, + 0.02191395108, + 0.02182523808, + 0.02185612208, + 0.02109890308, + 0.02294132008, + 0.02191512608, + 0.02264461208, + 0.02227651108, + 0.02307147508, + 0.02227169708, + 0.02177434208 + ], + "memory_usage_byte": [ + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3014656, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3141632, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3268608, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.021", + "mean": 0.022889189941111117, + "stddev": 0.0007161191938371117, + "median": 0.02280623708, + "user": 0.0009166992592592593, + "system": 0.0016941181481481477, + "min": 0.02132554808, + "max": 0.02453766808, + "times": [ + 0.02311599608, + 0.02274468508, + 0.02193879008, + 0.02158843608, + 0.02329398008, + 0.02379494508, + 0.02260801308, + 0.02439507908, + 0.02448522508, + 0.02403379508, + 0.02298143008, + 0.02263027308, + 0.02229235308, + 0.02335063508, + 0.02377098008, + 0.02269184108, + 0.023631199079999998, + 0.02338021508, + 0.02198521708, + 0.02251586208, + 0.022295963079999998, + 0.02226397608, + 0.02453766808, + 0.02184453408, + 0.02289659908, + 0.02382663208, + 0.02347397108, + 0.02225926308, + 0.02207640608, + 0.02243237108, + 0.02278192608, + 0.02270514808, + 0.02245069008, + 0.023018867079999998, + 0.02399866208, + 0.02236840708, + 0.02366382208, + 0.02294188908, + 0.02155127708, + 0.02294999808, + 0.02132554808, + 0.02242025908, + 0.02202766108, + 0.02182175108, + 0.02272186608, + 0.02211805308, + 0.02319764908, + 0.022308045079999998, + 0.02345400908, + 0.022437877079999998, + 0.02273417808, + 0.02217370908, + 0.02254318408, + 0.023269922079999998, + 0.02384951108, + 0.02419476108, + 0.02439866908, + 0.02354840508, + 0.02304219108, + 0.02354960608, + 0.02382648708, + 0.02345751208, + 0.02367913708, + 0.02253067208, + 0.02215132608, + 0.022603942079999998, + 0.02284062808, + 0.02252907808, + 0.02220393508, + 0.023291509079999998, + 0.02399456908, + 0.02407123208, + 0.02279175108, + 0.02300624708, + 0.02309500408, + 0.023036532079999998, + 0.02303833108, + 0.02316846908, + 0.02228349608, + 0.02247140608, + 0.022482600079999998, + 0.02370720808, + 0.02220123708, + 0.02230588608, + 0.02333678708, + 0.02153336008, + 0.02203071908, + 0.02279195108, + 0.02353659108, + 0.02267460708, + 0.022536274079999998, + 0.022769262079999998, + 0.02314857808, + 0.02194885908, + 0.02355038408, + 0.02320035308, + 0.02307451408, + 0.02379926408, + 0.02330480208, + 0.02257055708, + 0.02330320308, + 0.02303003208, + 0.02327859908, + 0.02171311608, + 0.02282052308, + 0.02170123708, + 0.02254831308, + 0.02235855408 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "command": "sleep 0.022", + "mean": 0.02415569324504855, + "stddev": 0.0009830972994273135, + "median": 0.02409406108, + "user": 0.001165289514563107, + "system": 0.001767603883495146, + "min": 0.02243173808, + "max": 0.02755932908, + "times": [ + 0.02456728108, + 0.02650439708, + 0.02480475408, + 0.02452974808, + 0.02300978308, + 0.02521451608, + 0.02543841408, + 0.02538411108, + 0.02475773908, + 0.02403843308, + 0.02426362708, + 0.02326921708, + 0.02447185308, + 0.02361749008, + 0.02410661008, + 0.02371481508, + 0.02327300908, + 0.02430165908, + 0.02328269108, + 0.02315262608, + 0.02380195808, + 0.02283639508, + 0.02491355808, + 0.02401717008, + 0.02556049408, + 0.02350359508, + 0.02400529208, + 0.02533555808, + 0.02467923308, + 0.02478442308, + 0.02422068708, + 0.02352175108, + 0.02481882108, + 0.02456148108, + 0.02314905108, + 0.024188183079999998, + 0.02483985908, + 0.02289141308, + 0.02364977308, + 0.02354907008, + 0.02379135508, + 0.026812933079999997, + 0.023360627079999998, + 0.02331436308, + 0.02504176308, + 0.02358805508, + 0.02409406108, + 0.02350689508, + 0.02303628508, + 0.02430972408, + 0.02516170908, + 0.02352843108, + 0.02274564308, + 0.02345165808, + 0.02429327308, + 0.02252948108, + 0.02445868508, + 0.02755932908, + 0.02522621808, + 0.02491753008, + 0.022858510079999998, + 0.02401968108, + 0.02409596908, + 0.02390450108, + 0.02373108808, + 0.027211489079999998, + 0.02537487108, + 0.02319182608, + 0.02390569508, + 0.02490164708, + 0.02384732708, + 0.02243173808, + 0.02367003008, + 0.02494288308, + 0.02436298308, + 0.02390639308, + 0.02423030808, + 0.02430082908, + 0.02320845908, + 0.02421546708, + 0.02530823508, + 0.02368935308, + 0.02306283708, + 0.023536658079999998, + 0.02359881208, + 0.02438320308, + 0.02477724008, + 0.02362231908, + 0.02419465008, + 0.02596891608, + 0.02307578608, + 0.02459456508, + 0.02384055408, + 0.02421387408, + 0.02510733208, + 0.02473580508, + 0.02243970708, + 0.02253156008, + 0.02550018108, + 0.02440877608, + 0.02281331608, + 0.02354148408, + 0.02352098308 + ], + "memory_usage_byte": [ + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680, + 3399680 + ], + "exit_codes": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ] +}