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_deploy.yml b/.circleci/ci_deploy.yml new file mode 100644 index 0000000000000..0c8ae5507187d --- /dev/null +++ b/.circleci/ci_deploy.yml @@ -0,0 +1,34 @@ +version: 2.1 + +jobs: + say-hello: + docker: + - image: cimg/base:current + + steps: + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +workflows: + say-hello-workflow: + jobs: + - say-hello + +- run: + name: Plan a deploy + command: | + circleci run release plan \ + --environment-name="" \ + --component-name="" \ + --target-version="" +# Your job here doing the actual deployment +- run: + name: Update a deploy to SUCCESS + command: circleci run release update --status=SUCCESS + when: on_success +- run: + name: Update planned deploy to FAILED + command: circleci run release update --status=FAILED + when: on_fail 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/cache/solidity-files-cache.json b/counter/cache/solidity-files-cache.json new file mode 100644 index 0000000000000..701e9c33edd3a --- /dev/null +++ b/counter/cache/solidity-files-cache.json @@ -0,0 +1 @@ +{"_format":"","paths":{"artifacts":"out","build_infos":"out/build-info","sources":"src","tests":"test","scripts":"script","libraries":["dependencies","lib"]},"files":{"lib/forge-std/src/Base.sol":{"lastModificationDate":1771494213591,"contentHash":"6f3a6f93872b8615","interfaceReprHash":null,"sourceName":"lib/forge-std/src/Base.sol","imports":["lib/forge-std/src/StdStorage.sol","lib/forge-std/src/Vm.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"CommonBase":{"0.8.33":{"default":{"path":"Base.sol/CommonBase.json","build_id":"895a4b3c7222d79a"}}},"ScriptBase":{"0.8.33":{"default":{"path":"Base.sol/ScriptBase.json","build_id":"895a4b3c7222d79a"}}},"TestBase":{"0.8.33":{"default":{"path":"Base.sol/TestBase.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/Script.sol":{"lastModificationDate":1771494213591,"contentHash":"19ad35825bea6fa0","interfaceReprHash":null,"sourceName":"lib/forge-std/src/Script.sol","imports":["lib/forge-std/src/Base.sol","lib/forge-std/src/StdChains.sol","lib/forge-std/src/StdCheats.sol","lib/forge-std/src/StdJson.sol","lib/forge-std/src/StdMath.sol","lib/forge-std/src/StdStorage.sol","lib/forge-std/src/StdStyle.sol","lib/forge-std/src/StdUtils.sol","lib/forge-std/src/Vm.sol","lib/forge-std/src/console.sol","lib/forge-std/src/console2.sol","lib/forge-std/src/interfaces/IMulticall3.sol","lib/forge-std/src/safeconsole.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"Script":{"0.8.33":{"default":{"path":"Script.sol/Script.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdAssertions.sol":{"lastModificationDate":1771494213593,"contentHash":"e29aa8aa08237766","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdAssertions.sol","imports":["lib/forge-std/src/Vm.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"StdAssertions":{"0.8.33":{"default":{"path":"StdAssertions.sol/StdAssertions.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdChains.sol":{"lastModificationDate":1771494213595,"contentHash":"9b363af4bbb6734a","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdChains.sol","imports":["lib/forge-std/src/Vm.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"StdChains":{"0.8.33":{"default":{"path":"StdChains.sol/StdChains.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdCheats.sol":{"lastModificationDate":1771494213598,"contentHash":"30325e8cda32c7ae","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdCheats.sol","imports":["lib/forge-std/src/StdStorage.sol","lib/forge-std/src/Vm.sol","lib/forge-std/src/console.sol","lib/forge-std/src/console2.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"StdCheats":{"0.8.33":{"default":{"path":"StdCheats.sol/StdCheats.json","build_id":"895a4b3c7222d79a"}}},"StdCheatsSafe":{"0.8.33":{"default":{"path":"StdCheats.sol/StdCheatsSafe.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdError.sol":{"lastModificationDate":1771494213599,"contentHash":"a1a86c7115e2cdf3","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdError.sol","imports":[],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"stdError":{"0.8.33":{"default":{"path":"StdError.sol/stdError.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdInvariant.sol":{"lastModificationDate":1771494213602,"contentHash":"0111ef959dff6f54","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdInvariant.sol","imports":[],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"StdInvariant":{"0.8.33":{"default":{"path":"StdInvariant.sol/StdInvariant.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdJson.sol":{"lastModificationDate":1771494213603,"contentHash":"5fb1b35c8fb281fd","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdJson.sol","imports":["lib/forge-std/src/Vm.sol"],"versionRequirement":">=0.6.0, <0.9.0","artifacts":{"stdJson":{"0.8.33":{"default":{"path":"StdJson.sol/stdJson.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdMath.sol":{"lastModificationDate":1771494213603,"contentHash":"72584abebada1e7a","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdMath.sol","imports":[],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"stdMath":{"0.8.33":{"default":{"path":"StdMath.sol/stdMath.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdStorage.sol":{"lastModificationDate":1771494213604,"contentHash":"c05daa9a55282c5b","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdStorage.sol","imports":["lib/forge-std/src/Vm.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"stdStorage":{"0.8.33":{"default":{"path":"StdStorage.sol/stdStorage.json","build_id":"895a4b3c7222d79a"}}},"stdStorageSafe":{"0.8.33":{"default":{"path":"StdStorage.sol/stdStorageSafe.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdStyle.sol":{"lastModificationDate":1771494213605,"contentHash":"ee166ef95092736e","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdStyle.sol","imports":["lib/forge-std/src/Vm.sol"],"versionRequirement":">=0.4.22, <0.9.0","artifacts":{"StdStyle":{"0.8.33":{"default":{"path":"StdStyle.sol/StdStyle.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdToml.sol":{"lastModificationDate":1771494213606,"contentHash":"fc667e4ecb7fa86c","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdToml.sol","imports":["lib/forge-std/src/Vm.sol"],"versionRequirement":">=0.6.0, <0.9.0","artifacts":{"stdToml":{"0.8.33":{"default":{"path":"StdToml.sol/stdToml.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/StdUtils.sol":{"lastModificationDate":1771494213606,"contentHash":"804c508a1dad250e","interfaceReprHash":null,"sourceName":"lib/forge-std/src/StdUtils.sol","imports":["lib/forge-std/src/Vm.sol","lib/forge-std/src/interfaces/IMulticall3.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"StdUtils":{"0.8.33":{"default":{"path":"StdUtils.sol/StdUtils.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/Test.sol":{"lastModificationDate":1771494213607,"contentHash":"a18f53966ac7b768","interfaceReprHash":null,"sourceName":"lib/forge-std/src/Test.sol","imports":["lib/forge-std/src/Base.sol","lib/forge-std/src/StdAssertions.sol","lib/forge-std/src/StdChains.sol","lib/forge-std/src/StdCheats.sol","lib/forge-std/src/StdError.sol","lib/forge-std/src/StdInvariant.sol","lib/forge-std/src/StdJson.sol","lib/forge-std/src/StdMath.sol","lib/forge-std/src/StdStorage.sol","lib/forge-std/src/StdStyle.sol","lib/forge-std/src/StdToml.sol","lib/forge-std/src/StdUtils.sol","lib/forge-std/src/Vm.sol","lib/forge-std/src/console.sol","lib/forge-std/src/console2.sol","lib/forge-std/src/interfaces/IMulticall3.sol","lib/forge-std/src/safeconsole.sol"],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"Test":{"0.8.33":{"default":{"path":"Test.sol/Test.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/Vm.sol":{"lastModificationDate":1771494213611,"contentHash":"ad5eadc2275eb655","interfaceReprHash":null,"sourceName":"lib/forge-std/src/Vm.sol","imports":[],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"Vm":{"0.8.33":{"default":{"path":"Vm.sol/Vm.json","build_id":"895a4b3c7222d79a"}}},"VmSafe":{"0.8.33":{"default":{"path":"Vm.sol/VmSafe.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/console.sol":{"lastModificationDate":1771494213613,"contentHash":"bae85493a76fb054","interfaceReprHash":null,"sourceName":"lib/forge-std/src/console.sol","imports":[],"versionRequirement":">=0.4.22, <0.9.0","artifacts":{"console":{"0.8.33":{"default":{"path":"console.sol/console.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/console2.sol":{"lastModificationDate":1771494213614,"contentHash":"49a7da3dfc404603","interfaceReprHash":null,"sourceName":"lib/forge-std/src/console2.sol","imports":["lib/forge-std/src/console.sol"],"versionRequirement":">=0.4.22, <0.9.0","artifacts":{},"seenByCompiler":true},"lib/forge-std/src/interfaces/IMulticall3.sol":{"lastModificationDate":1771494213622,"contentHash":"b680a332ebf10901","interfaceReprHash":null,"sourceName":"lib/forge-std/src/interfaces/IMulticall3.sol","imports":[],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"IMulticall3":{"0.8.33":{"default":{"path":"IMulticall3.sol/IMulticall3.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"lib/forge-std/src/safeconsole.sol":{"lastModificationDate":1771494213625,"contentHash":"621653b34a6691ea","interfaceReprHash":null,"sourceName":"lib/forge-std/src/safeconsole.sol","imports":[],"versionRequirement":">=0.6.2, <0.9.0","artifacts":{"safeconsole":{"0.8.33":{"default":{"path":"safeconsole.sol/safeconsole.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"script/Counter.s.sol":{"lastModificationDate":1777705802269,"contentHash":"0c79311668a3de55","interfaceReprHash":null,"sourceName":"script/Counter.s.sol","imports":["lib/forge-std/src/Base.sol","lib/forge-std/src/Script.sol","lib/forge-std/src/StdChains.sol","lib/forge-std/src/StdCheats.sol","lib/forge-std/src/StdJson.sol","lib/forge-std/src/StdMath.sol","lib/forge-std/src/StdStorage.sol","lib/forge-std/src/StdStyle.sol","lib/forge-std/src/StdUtils.sol","lib/forge-std/src/Vm.sol","lib/forge-std/src/console.sol","lib/forge-std/src/console2.sol","lib/forge-std/src/interfaces/IMulticall3.sol","lib/forge-std/src/safeconsole.sol","src/Counter.sol"],"versionRequirement":"^0.8.13","artifacts":{"CounterScript":{"0.8.33":{"default":{"path":"Counter.s.sol/CounterScript.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"src/Counter.sol":{"lastModificationDate":1777705802271,"contentHash":"11a918b87c723f51","interfaceReprHash":null,"sourceName":"src/Counter.sol","imports":[],"versionRequirement":"^0.8.13","artifacts":{"Counter":{"0.8.33":{"default":{"path":"Counter.sol/Counter.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true},"test/Counter.t.sol":{"lastModificationDate":1777705802273,"contentHash":"a86433db55687469","interfaceReprHash":null,"sourceName":"test/Counter.t.sol","imports":["lib/forge-std/src/Base.sol","lib/forge-std/src/StdAssertions.sol","lib/forge-std/src/StdChains.sol","lib/forge-std/src/StdCheats.sol","lib/forge-std/src/StdError.sol","lib/forge-std/src/StdInvariant.sol","lib/forge-std/src/StdJson.sol","lib/forge-std/src/StdMath.sol","lib/forge-std/src/StdStorage.sol","lib/forge-std/src/StdStyle.sol","lib/forge-std/src/StdToml.sol","lib/forge-std/src/StdUtils.sol","lib/forge-std/src/Test.sol","lib/forge-std/src/Vm.sol","lib/forge-std/src/console.sol","lib/forge-std/src/console2.sol","lib/forge-std/src/interfaces/IMulticall3.sol","lib/forge-std/src/safeconsole.sol","src/Counter.sol"],"versionRequirement":"^0.8.13","artifacts":{"CounterTest":{"0.8.33":{"default":{"path":"Counter.t.sol/CounterTest.json","build_id":"895a4b3c7222d79a"}}}},"seenByCompiler":true}},"builds":["895a4b3c7222d79a"],"profiles":{"default":{"solc":{"optimizer":{"enabled":false,"runs":200},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["abi","evm.bytecode.object","evm.bytecode.sourceMap","evm.bytecode.linkReferences","evm.deployedBytecode.object","evm.deployedBytecode.sourceMap","evm.deployedBytecode.linkReferences","evm.deployedBytecode.immutableReferences","evm.methodIdentifiers","metadata"]}},"evmVersion":"osaka","viaIR":false,"libraries":{}},"vyper":{"evmVersion":"osaka","outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode"]}}}}},"preprocessed":false,"mocks":[]} \ No newline at end of file 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..3b901235258ae 100644 --- a/crates/cast/src/cmd/miner.rs +++ b/crates/cast/src/cmd/miner.rs @@ -23,8 +23,8 @@ where handles.push(std::thread::spawn(move || { #[repr(C)] - struct B256Aligned(B256, [usize; 0]); - +#[repr(C, align(8))] +struct B256Aligned(B256); let mut salt = B256Aligned(salt, []); // SAFETY: `B256` is aligned to `usize`. let salt_word = unsafe { 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/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index 42b91ccc366aa..e70f47e174a81 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -205,6 +205,12 @@ impl Comments { } } +impl From> for Comments { + fn from(value: Vec) -> Self { + Self(value) + } +} + /// The collection of references to natspec [Comment] items. #[derive(Debug, Default, PartialEq, Eq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); 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/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 5d0f2822dad24..765df8d8eacba 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1319,7 +1319,7 @@ impl Inspector> .journal() .evm_state() .get(&create.caller()) - .map(|acc| create.caller().create(acc.info.nonce)); + .map(|acc| create.caller().create(acc.info.nonce.saturating_sub(1))); let (result, address) = self.transact_inner( ecx, diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs index b45f74eba9528..350ad68e02f8e 100644 --- a/crates/evm/fuzz/src/invariant/mod.rs +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -266,14 +266,10 @@ pub struct InvariantContract<'a> { pub address: Address, /// Name of the test contract. pub name: &'a str, - /// Invariant functions to assert against, paired with their `fail_on_revert` config. - /// Stored in **source declaration order** so failure-event attribution and report - /// rendering match user expectations. + /// Invariant function present in the test contract. + pub primary_invariant_fn: &'a Function, + /// All invariant functions present in the test contract and their fail on revert config. pub invariant_fns: Vec<(&'a Function, bool)>, - /// Index into [`Self::invariant_fns`] of the campaign anchor — the function chosen by - /// the `--mt` filter (or the only one). Used for corpus and persistence file paths and - /// for the legacy single-invariant `TestResult.{reason, counterexample}` fields. - pub anchor_idx: usize, /// If true, `afterInvariant` function is called after each invariant run. pub call_after_invariant: bool, /// ABI of the test contract. @@ -282,28 +278,20 @@ pub struct InvariantContract<'a> { impl<'a> InvariantContract<'a> { /// Creates a new invariant contract. - /// - /// Caller must ensure `invariant_fns` is non-empty and `anchor_idx < invariant_fns.len()`. pub const fn new( address: Address, name: &'a str, + primary_invariant_fn: &'a Function, invariant_fns: Vec<(&'a Function, bool)>, - anchor_idx: usize, call_after_invariant: bool, abi: &'a JsonAbi, ) -> Self { - Self { address, name, invariant_fns, anchor_idx, call_after_invariant, abi } - } - - /// Returns the campaign anchor — the invariant matched by `--mt` (or the only one). - /// Used for corpus and persistence file paths and for legacy primary `TestResult` fields. - pub fn anchor(&self) -> &'a Function { - self.invariant_fns[self.anchor_idx].0 + Self { address, name, primary_invariant_fn, invariant_fns, call_after_invariant, abi } } /// Returns true if this is an optimization mode invariant (returns int256). pub fn is_optimization(&self) -> bool { - is_optimization_invariant(self.anchor()) + is_optimization_invariant(self.primary_invariant_fn) } } 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/build.rs b/crates/forge/src/cmd/build.rs index 70f76978d0e1a..2aea7fd0830b6 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -23,7 +23,6 @@ use foundry_config::{ filter::expand_globs, }; use serde::Serialize; -use solar::{interface::Session, sema::Compiler}; use std::path::PathBuf; foundry_config::merge_impl_figment_convert!(BuildArgs, build); @@ -132,7 +131,10 @@ impl BuildArgs { && let Err(err) = self.lint(&project, &config, self.paths.as_deref(), &mut output) { emit_lint_failure_notice(); - return Err(err.wrap_err("post-build lint step failed")); + return Err(err.wrap_err( + "post-build lint step failed; rerun with --no-lint or set \ + `lint_on_build = false` under `[lint]` in foundry.toml to bypass", + )); } Ok(output) @@ -201,7 +203,13 @@ impl BuildArgs { // NOTE(rusowsky): Once solar can drop unsupported versions, rather than creating a new // compiler, we should reuse the parser from the project output. - let mut compiler = Compiler::new(Session::builder().with_stderr_emitter().build()); + // + // Buffer emitter so parse-phase errors surface verbatim in `convert_solar_errors`. + let mut compiler = solar::sema::Compiler::new( + solar::interface::Session::builder() + .with_buffer_emitter(Default::default()) + .build(), + ); // Load the solar-compatible sources to the pcx before linting compiler.enter_mut(|compiler| { @@ -211,6 +219,17 @@ impl BuildArgs { pcx.parse(); }); + // Flush buffered parse-phase warnings; on error, `convert_solar_errors` surfaces + // them in the returned error instead, so skip to avoid duplicates. + if compiler.sess().dcx.has_errors().is_ok() + && let Some(diags) = compiler.sess().emitted_diagnostics() + { + let s = diags.to_string(); + if !s.is_empty() { + let _ = sh_eprint!("{s}"); + } + } + linter.lint(&input_files, config.deny, &mut compiler)?; } @@ -337,13 +356,9 @@ impl BuildArgs { /// Notice shown on lint-on-build failure; printed separately so it survives single-line /// cause-chain rendering. const LINT_FAILURE_NOTICE: &str = "\ -note: internal lint engine failure (compilation itself succeeded). -note: please file a bug report at - https://github.com/foundry-rs/foundry/issues/new?template=BUG-FORM.yml - and attach the full output above. -help: rerun with `--no-lint` to skip linting for this build, or consider temporarily - disabling forge lint on build: - https://getfoundry.sh/forge/linting#disable-linting-on-build +note: post-build lint failed, but compilation succeeded. +bypass with `--no-lint` or set `lint_on_build = false` under `[lint]` in foundry.toml +docs: https://getfoundry.sh/forge/linting#disable-linting-on-build "; fn emit_lint_failure_notice() { 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/lint.rs b/crates/forge/tests/cli/lint.rs index 49a63ff7a9012..dcf30e34827b5 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -837,8 +837,9 @@ Warning (2018): Function state mutability can be restricted to pure "#]]); }); -// Lint diagnostics produced during `forge build` must stream to stderr through the emitter -// installed inside `SolidityLinter::lint`. +// Regression test: after switching the lint session to a buffer emitter, lint diagnostics +// produced during `forge build` must still stream to stderr (they are emitted through a +// separate emitter installed inside `SolidityLinter::lint`, not the session emitter). forgetest!(build_emits_lint_diagnostics, |prj, cmd| { prj.add_source("CounterAWithLints", COUNTER_A); @@ -914,54 +915,6 @@ Warning (2018): Function state mutability can be restricted to pure ]]); }); -// `deny = notes` + an info-level lint forces the linter to return Err, exercising the same -// failure-notice path an unhandled solar edge case would take. -forgetest!(build_emits_lint_failure_notice_on_failure, |prj, cmd| { - prj.add_source("CounterAWithLints", COUNTER_A); - - prj.update_config(|config| { - config.lint.severity = vec![LintSeverity::Info]; - config.deny = DenyLevel::Notes; - }); - - cmd.arg("build").assert_failure().stderr_eq(str![[r#" -note[mixed-case-variable]: mutable variables should use mixedCase - [FILE]:6:20 - │ -6 │ uint256 public CounterA_Fail_Lint; - │ ━━━━━━━━━━━━━━━━━━ help: consider using: `counterAFailLint` - │ - ╰ help: https://getfoundry.sh/forge/linting/mixed-case-variable - - -note: internal lint engine failure (compilation itself succeeded). -note: please file a bug report at - https://github.com/foundry-rs/foundry/issues/new?template=BUG-FORM.yml - and attach the full output above. -help: rerun with `--no-lint` to skip linting for this build, or consider temporarily - disabling forge lint on build: - https://getfoundry.sh/forge/linting#disable-linting-on-build - -Error: post-build lint step failed - -Context: -- aborting due to 1 linter note(s) - -"#]]); -}); - -// Same setup as above, but `--no-lint` skips the lint step so the failure notice never fires. -forgetest!(build_no_lint_flag_does_not_emit_lint_failure_notice, |prj, cmd| { - prj.add_source("CounterAWithLints", COUNTER_A); - - prj.update_config(|config| { - config.lint.severity = vec![LintSeverity::Info]; - config.deny = DenyLevel::Notes; - }); - - cmd.args(["build", "--no-lint"]).assert_success().stderr_eq(str![[r#""#]]); -}); - forgetest!(can_process_inline_config_regardless_of_input_order, |prj, cmd| { prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); 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 + ] + } + ] +}