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..4168efef0971f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,32 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello + 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/workflows/Docker.yml b/.github/workflows/Docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/Docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + 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..3bd2977566162 --- /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@8f66ce948afb779712a1d57ecb0a528d7ded5a45 + 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/ci.yml b/.github/workflows/ci.yml index a0a5d931f194c..d151c2a0c5ffb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: crate-ci/typos@bbaefadf97b0ec5fdc942684b647f1a6ab250274 # v1.46.0 + - uses: crate-ci/typos@5374cbf686e897b15713110e233094e2874de7ef # v1.46.1 shellcheck: runs-on: depot-ubuntu-latest 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/crate-checks.yml b/.github/workflows/crate-checks.yml index acd8a72327090..829823e85db78 100644 --- a/.github/workflows/crate-checks.yml +++ b/.github/workflows/crate-checks.yml @@ -29,7 +29,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@cca35edeb1d01366c2843b68fc3ca441446d73d3 # v2.77.1 + - uses: taiki-e/install-action@e3134ec54b36203e18f2d1e80652058bd078dd91 # v2.77.3 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 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.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000..5a2330e7d5d62 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,62 @@ +name: Docker + +on: + push: + tags: ["*"] + branches: + - "main" + 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..1295e430ca96a --- /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@6051de21ad50fbb1767bc93c11357a49082ad116' # 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/nix.yml b/.github/workflows/nix.yml index 526f8c1d2dbae..e50e73d41dc3a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,7 +19,7 @@ jobs: contents: write pull-requests: write steps: - - uses: DeterminateSystems/determinate-nix-action@7c4cc317e802185875512bfcb68259257279d767 # v3.19.1 + - uses: DeterminateSystems/determinate-nix-action@bafaa638b9d5ec0e7e3ac1a7fc80453ef1fd265f # v3.20.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: permissions: contents: read steps: - - uses: DeterminateSystems/determinate-nix-action@7c4cc317e802185875512bfcb68259257279d767 # v3.19.1 + - uses: DeterminateSystems/determinate-nix-action@bafaa638b9d5ec0e7e3ac1a7fc80453ef1fd265f # v3.20.0 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 323059e99e6b6..e3a5b385a28a3 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -137,6 +137,36 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || inputs.run_id }} + - name: Validate Downloaded Artifacts + env: + ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} + run: | + set -euo pipefail + + echo "Validating artifacts in: $ARTIFACT_DIR" + + if [[ ! -d "$ARTIFACT_DIR" ]]; then + echo "ERROR: Artifact directory does not exist: $ARTIFACT_DIR" >&2 + exit 1 + fi + + if ! find "$ARTIFACT_DIR" -mindepth 1 -print -quit | grep -q .; then + echo "ERROR: Artifact directory is empty: $ARTIFACT_DIR" >&2 + exit 1 + fi + + # Reject files with suspicious paths (absolute paths or parent directory traversals) + # Use null-delimited paths to safely handle filenames with newlines or whitespace + while IFS= read -r -d '' path; do + rel="${path#"$ARTIFACT_DIR"/}" + if [[ "$rel" == /* ]] || [[ "$rel" == *".."* ]]; then + echo "ERROR: Suspicious artifact path detected: $rel" >&2 + exit 1 + fi + done < <(find "$ARTIFACT_DIR" -type f -print0) + + echo "Artifact validation completed successfully." + - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: 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/test-flaky.yml b/.github/workflows/test-flaky.yml index db8eb456a03fe..30d7ef9af917d 100644 --- a/.github/workflows/test-flaky.yml +++ b/.github/workflows/test-flaky.yml @@ -33,7 +33,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@cca35edeb1d01366c2843b68fc3ca441446d73d3 # v2.77.1 + - uses: taiki-e/install-action@e3134ec54b36203e18f2d1e80652058bd078dd91 # v2.77.3 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -67,7 +67,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@cca35edeb1d01366c2843b68fc3ca441446d73d3 # v2.77.1 + - uses: taiki-e/install-action@e3134ec54b36203e18f2d1e80652058bd078dd91 # v2.77.3 with: tool: nextest - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92928fd732076..75e35f278bf65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,7 +73,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 - - uses: taiki-e/install-action@cca35edeb1d01366c2843b68fc3ca441446d73d3 # v2.77.1 + - uses: taiki-e/install-action@e3134ec54b36203e18f2d1e80652058bd078dd91 # v2.77.3 with: tool: nextest 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..7ed8807cbf0f5 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -141,10 +141,20 @@ impl BenchmarkProject { 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) { + 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/.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..85198aaa55b84 --- /dev/null +++ b/counter/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env 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/foundry.toml b/counter/foundry.toml new file mode 100644 index 0000000000000..25b918f9c9a96 --- /dev/null +++ b/counter/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# 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/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/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/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/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/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 41c16794c3cce..ee1929b1057a2 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1298,7 +1298,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/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/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..2c11e0222a286 --- /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 daignostic 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 `LintCotext`. +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/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/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 06f83886d0fd2..c1a6cb53bdbff 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -121,9 +121,32 @@ impl ScriptTester { let to_dir = root.join("utils"); fs::create_dir_all(&to_dir)?; for entry in fs::read_dir(&from_dir)? { - let file = &entry?.path(); - let name = file.file_name().unwrap(); - fs::copy(file, to_dir.join(name))?; + let file = entry?.path(); + // Only operate on regular files to avoid following symlinks or directories + let metadata = fs::symlink_metadata(&file)?; + let ftype = metadata.file_type(); + if !ftype.is_file() { + continue; + } + let name = match file.file_name() { + Some(name) => name, + None => continue, + }; + // 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; + } + // 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) { + continue; + } + } else { + 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/npm/scripts/stage-from-artifact.mjs b/npm/scripts/stage-from-artifact.mjs index c1ca22c8bb2ed..1d39fdc82e84f 100755 --- a/npm/scripts/stage-from-artifact.mjs +++ b/npm/scripts/stage-from-artifact.mjs @@ -64,10 +64,10 @@ function resolveArgs() { strict: true }) - const tool = requireValue(values.tool || process.env.TARGET_TOOL, 'tool') - const platform = requireValue(values.platform || process.env.PLATFORM_NAME, 'platform') - const arch = requireValue(values.arch || process.env.ARCH, 'arch') - const releaseVersion = requireValue( + const tool = requireSafeIdentifier(values.tool || process.env.TARGET_TOOL, 'tool') + const platform = requireSafeIdentifier(values.platform || process.env.PLATFORM_NAME, 'platform') + const arch = requireSafeIdentifier(values.arch || process.env.ARCH, 'arch') + const releaseVersion = requireSafeIdentifier( values.release || values['release-version'] || process.env.RELEASE_VERSION, 'release version' ) @@ -95,6 +95,26 @@ function requireValue(value, name) { throw new Error(`Missing required ${name}`) } +/** + * Ensure a required value is present and consists only of safe identifier + * characters suitable for use in file and directory names. + * + * Allowed characters: letters, digits, dot, underscore, and hyphen. + * + * @param {string | undefined} value + * @param {string} name + * @returns {string} + */ +function requireSafeIdentifier(value, name) { + const trimmed = requireValue(value, name) + if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) { + throw new Error( + `Invalid ${name}: "${trimmed}". Only letters, digits, ".", "_", and "-" are allowed.` + ) + } + return trimmed +} + /** * Determine which archive variant exists for the given artifact prefix. * @param {string} prefix diff --git a/npm/src/const.mjs b/npm/src/const.mjs index 6b3dcf3f9fbed..e606759888acb 100644 --- a/npm/src/const.mjs +++ b/npm/src/const.mjs @@ -1,4 +1,5 @@ import * as NodePath from 'node:path' +import { URL } from 'node:url' /** * @typedef {'amd64' | 'arm64'} Arch @@ -33,11 +34,36 @@ export function resolveTargetTool(raw = process.env.TARGET_TOOL || process.argv[ export function getRegistryUrl() { // Prefer npm's configured registry (works with Verdaccio and custom registries) // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( + const raw = process.env.npm_config_registry || process.env.REGISTRY_URL || 'https://registry.npmjs.org' - ) + + let parsed + try { + parsed = new URL(raw) + } catch { + throw new Error(`Invalid registry URL: "${raw}"`) + } + + // Enforce secure scheme + if (parsed.protocol !== 'https:') { + throw new Error(`Insecure registry URL scheme "${parsed.protocol}". Only "https:" is allowed.`) + } + + // Basic SSRF mitigation: disallow obvious loopback hosts + const hostname = parsed.hostname.toLowerCase() + if ( + hostname === 'localhost' + || hostname === '127.0.0.1' + || hostname === '::1' + ) { + throw new Error(`Registry URL host "${parsed.hostname}" is not allowed.`) + } + + // Normalize to a consistent base URL without trailing slash + const base = parsed.origin + parsed.pathname + return base.replace(/\/+$/, '') } /** 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 + ] + } + ] +}