diff --git a/.codespellignore b/.codespellignore deleted file mode 100644 index 75829846c..000000000 --- a/.codespellignore +++ /dev/null @@ -1,3 +0,0 @@ -keypair -astroid -uvloop diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b03713d5c..4ba48f266 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,8 +12,8 @@ "python.formatting.blackPath": "/usr/local/py-utils/bin/black", "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", "editor.defaultFormatter": "trunk.io", - "cairols.sourceDir": "src", - "cairols.cairoPath": ["src", "tests"] + "cairols.sourceDir": "cairo_zero", + "cairols.cairoPath": ["cairo_zero", "tests"] }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ @@ -21,14 +21,15 @@ "ms-python.vscode-pylance", "starkware.cairo", "ericglau.cairo-ls", - "trunk.io" + "trunk.io", + "starkware.cairo1" ] } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && curl -L https://foundry.paradigm.xyz | bash && /home/vscode/.foundry/bin/foundryup && make setup", + "postCreateCommand": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && curl -L https://foundry.paradigm.xyz | bash && /home/vscode/.foundry/bin/foundryup && curl -LsSf https://astral.sh/uv/install.sh | sh && . $HOME/.cargo/env && uv venv && make setup && curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode", "features": { diff --git a/.env.example b/.env.example index 9381ce47e..80cc4030d 100644 --- a/.env.example +++ b/.env.example @@ -52,3 +52,7 @@ HYPOTHESIS_PROFILE=dev VOYAGER_API_URL= VOYAGER_API_KEY= + +# Private key to sign transactions for RLP scripts/compute_rlp_encoding.ts +# Warning: Do not use this key in production systems +PRIVATE_KEY_RLP_SCRIPT=0x6be0067ba259624aa28f796b703e7d095925a470b17786767bd09aaa3fc2ceea diff --git a/.github/workflows/ci.yml b/.github/workflows/cairo-zero-ci.yml similarity index 97% rename from .github/workflows/ci.yml rename to .github/workflows/cairo-zero-ci.yml index 0209316b4..bf3a4b43e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/cairo-zero-ci.yml @@ -1,16 +1,18 @@ -name: CI +name: cairo-zero-CI on: push: branches: - main - pull_request: {} + pull_request: + paths: + - cairo_zero/** env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: cairo-zero-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: read-all @@ -32,7 +34,7 @@ jobs: with: filters: | src: - - 'src/**' + - 'cairo_zero/**' kakarot_scripts: - 'kakarot_scripts/**' solidity: @@ -96,7 +98,7 @@ jobs: - name: Run tests env: HYPOTHESIS_PROFILE: ci - run: make test-unit + run: make test-unit-cairo-zero - name: Upload coverage report to codecov uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/nightly-fuzzing.yml b/.github/workflows/cairo-zero-nightly-fuzzing.yml similarity index 92% rename from .github/workflows/nightly-fuzzing.yml rename to .github/workflows/cairo-zero-nightly-fuzzing.yml index 9083bd3cd..506b9acf6 100644 --- a/.github/workflows/nightly-fuzzing.yml +++ b/.github/workflows/cairo-zero-nightly-fuzzing.yml @@ -1,4 +1,4 @@ -name: NIGHTLY-FUZZING +name: cairo-zero-NIGHTLY-FUZZING on: schedule: @@ -30,7 +30,7 @@ jobs: - name: Run tests env: HYPOTHESIS_PROFILE: nightly - run: make test-unit + run: make test-unit-cairo-zero - name: Upload coverage report to codecov uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/cairo-zero-release.yml similarity index 99% rename from .github/workflows/release.yml rename to .github/workflows/cairo-zero-release.yml index dca8189d5..bc29c36ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/cairo-zero-release.yml @@ -1,5 +1,5 @@ # trunk-ignore-all(checkov/CKV2_GHA_1) -name: Release +name: cairo-zero-Release on: release: diff --git a/.github/workflows/update-rpc.yml b/.github/workflows/cairo-zero-update-rpc.yml similarity index 97% rename from .github/workflows/update-rpc.yml rename to .github/workflows/cairo-zero-update-rpc.yml index 3a59bfb50..23c6e2621 100644 --- a/.github/workflows/update-rpc.yml +++ b/.github/workflows/cairo-zero-update-rpc.yml @@ -1,4 +1,4 @@ -name: Update Submodule +name: cairo-zero Update Submodule on: release: diff --git a/.github/workflows/ssj-build.yml b/.github/workflows/ssj-build.yml new file mode 100644 index 000000000..00353104f --- /dev/null +++ b/.github/workflows/ssj-build.yml @@ -0,0 +1,34 @@ +name: Reusable Build Workflow + +on: + workflow_call: + inputs: + artifact-name: + required: true + type: string + +permissions: read-all + +jobs: + ssj-build: + runs-on: ubuntu-latest + env: + CI_COMMIT_MESSAGE: CI Formatting Auto Commit + CI_COMMIT_AUTHOR: ${{ github.event.repository.name }} CI + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Scarb + uses: software-mansion/setup-scarb@v1 + with: + tool-versions: ./cairo/kakarot-ssj/.tool-versions + + - name: Build contracts + run: cd cairo/kakarot-ssj/ && scarb build -p contracts + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.artifact-name }} + path: cairo/kakarot-ssj/target/dev diff --git a/.github/workflows/ssj-ci.yml b/.github/workflows/ssj-ci.yml new file mode 100644 index 000000000..441ec550a --- /dev/null +++ b/.github/workflows/ssj-ci.yml @@ -0,0 +1,84 @@ +name: SSJ-CI + +on: + push: + branches: + - main + paths: + - cairo/kakarot-ssj/** + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +concurrency: + group: ssj-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: read-all + +jobs: + ssj-build: + uses: ./.github/workflows/ssj-build.yml + with: + artifact-name: ssj-build + + ssj-tests-unit: + uses: ./.github/workflows/ssj-tests-unit.yml + with: + run-fmt-check: false + + ssj-ef-tests: + uses: ./.github/workflows/ssj-ef-tests.yml + needs: [ssj-build] + with: + artifact-name: ssj-build + + ssj-resources: + runs-on: ubuntu-latest + needs: [ssj-ef-tests] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.10.14 + uses: actions/setup-python@v4 + with: + python-version: 3.10.14 + + - name: Load cached Poetry installation + id: cached-poetry + uses: actions/cache@v4 + with: + path: ~/.local + key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install Poetry + if: steps.cached-poetry.outputs.cache-hit != 'true' + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - run: poetry config installer.modern-installation false + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache@v4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: make setup + + - name: Load performance artifacts + uses: actions/download-artifact@v3 + with: + path: resources + name: resources + + - name: Check resources evolution + run: | + result=$(GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} make check-resources 2>&1) + echo "$result" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/ssj-ef-tests.yml b/.github/workflows/ssj-ef-tests.yml new file mode 100644 index 000000000..6af3f2914 --- /dev/null +++ b/.github/workflows/ssj-ef-tests.yml @@ -0,0 +1,129 @@ +name: SSJ-EF-Tests + +on: + workflow_call: + inputs: + artifact-name: + required: true + type: string + +permissions: read-all + +jobs: + ef-tests: + # trunk-ignore(actionlint/runner-label) + runs-on: ubuntu-latest-16-cores + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache cairo-native setup + id: cache-cairo-native + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + cairo/kakarot-ssj/target/ + ./libcairo_native_runtime.a + key: + ${{ runner.os }}-cairo-native-${{ hashFiles('**/Cargo.lock', + 'scripts/setup_cairo_native.sh') }} + + - name: Make setup script executable + run: chmod +x ./cairo/kakarot-ssj/scripts/setup_cairo_native.sh + + - name: Setup Cairo Native + run: | + if [[ "${{ steps.cache-cairo-native.outputs.cache-hit }}" == 'true' ]]; then + sudo ./cairo/kakarot-ssj/scripts/setup_cairo_native.sh -s + else + sudo ./cairo/kakarot-ssj/scripts/setup_cairo_native.sh + fi + + - name: Set Environment Variables + run: | + echo "MLIR_SYS_190_PREFIX=/usr/lib/llvm-19/" >> $GITHUB_ENV + echo "LLVM_SYS_191_PREFIX=/usr/lib/llvm-19/" >> $GITHUB_ENV + echo "TABLEGEN_190_PREFIX=/usr/lib/llvm-19/" >> $GITHUB_ENV + echo "CAIRO_NATIVE_RUNTIME_LIBRARY=$(pwd)/libcairo_native_runtime.a" >> $GITHUB_ENV + + - name: Checkout ef-tests + uses: actions/checkout@v4 + with: + repository: kkrt-labs/ef-tests + ref: feat/cairo-native + path: ef-tests # Check out to a subdirectory to avoid cleaning the kakarot-ssj directory + + - name: Checkout local skip file + uses: actions/checkout@v4 + with: + sparse-checkout: | + cairo/kakarot-ssj/ + sparse-checkout-cone-mode: false + path: skip-file + + - name: Setup ef-tests + run: | + mv skip-file/cairo/kakarot-ssj/blockchain-tests-skip.yml ef-tests/blockchain-tests-skip.yml + cd ef-tests + mkdir -p build/common + make setup setup-kakarot-v0 + + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Download Kakarot-SSJ build artifacts in v1 + uses: actions/download-artifact@v3 + with: + name: ${{ inputs.artifact-name }} + path: ef-tests/build/v1 + + - name: Move Cairo1Helpers + run: | + mv ef-tests/build/v1/contracts_Cairo1Helpers.compiled_contract_class.json \ + ef-tests/build/common/cairo1_helpers.json + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.10.14 + + # Add this step to verify the file exists + - name: Verify libcairo_native_runtime.a + run: | + echo $CAIRO_NATIVE_RUNTIME_LIBRARY + ls -l $CAIRO_NATIVE_RUNTIME_LIBRARY + + - name: Run tests + working-directory: ef-tests + run: | + set -o pipefail + RUST_MIN_STACK=1342177280 make ef-test-v1-native | tee test_v1.out + set +o pipefail + + - name: Retrieve ef-tests execution resources + working-directory: ef-tests + run: python scripts/compute_resources.py + env: + KAKAROT_VERSION: v1 + + - name: Upload resources + uses: actions/upload-artifact@v3 + with: + path: ef-tests/resources + name: resources + + - name: Generate blockchain-tests-skip.yml file + if: github.event_name == 'workflow_dispatch' + working-directory: ef-tests + run: make generate-skip-file + + - name: Upload skip file + if: github.event_name == 'workflow_dispatch' + uses: actions/upload-artifact@v3 + with: + path: ef-tests/blockchain-tests-skip.yml + name: blockchain-tests-skip diff --git a/.github/workflows/ssj-release.yml b/.github/workflows/ssj-release.yml new file mode 100644 index 000000000..3e4da5724 --- /dev/null +++ b/.github/workflows/ssj-release.yml @@ -0,0 +1,30 @@ +# trunk-ignore-all(checkov/CKV2_GHA_1) +name: SSJ-Release + +on: + release: + types: [published] + +jobs: + build-and-upload: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Scarb + uses: software-mansion/setup-scarb@v1 + with: + tool-versions: ./cairo/kakarot-ssj/.tool-versions + - name: Build contracts + run: | + cd cairo/kakarot-ssj && scarb build -p contracts + - name: Zip dev artifacts + run: zip -rj kakarot-ssj-build.zip cairo/kakarot-ssj/target/dev + - name: Upload artifacts to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: kakarot-ssj-build.zip + asset_name: kakarot-ssj-build.zip + tag: ${{ github.ref_name }} + overwrite: true diff --git a/.github/workflows/ssj-test.yml b/.github/workflows/ssj-test.yml new file mode 100644 index 000000000..b3dc169c7 --- /dev/null +++ b/.github/workflows/ssj-test.yml @@ -0,0 +1,29 @@ +name: SSJ-CI + +permissions: read-all + +on: + pull_request: + paths: + - cairo/kakarot-ssj/** + +concurrency: + group: ssj-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ssj-build: + uses: ./.github/workflows/ssj-build.yml + with: + artifact-name: ssj-build + + ssj-tests-unit: + uses: ./.github/workflows/ssj-tests-unit.yml + with: + run-fmt-check: true + + ssj-ef-tests: + uses: ./.github/workflows/ssj-ef-tests.yml + needs: [ssj-build] + with: + artifact-name: ssj-build diff --git a/.github/workflows/ssj-tests-unit.yml b/.github/workflows/ssj-tests-unit.yml new file mode 100644 index 000000000..ab9068492 --- /dev/null +++ b/.github/workflows/ssj-tests-unit.yml @@ -0,0 +1,28 @@ +name: Reusable Unit Tests Workflow + +on: + workflow_call: + inputs: + run-fmt-check: + type: boolean + default: false + required: false + +permissions: read-all + +jobs: + unit-tests: + runs-on: ubuntu-latest-16-cores # trunk-ignore(actionlint/runner-label) + steps: + - uses: actions/checkout@v4 + - uses: foundry-rs/setup-snfoundry@v3 + - uses: software-mansion/setup-scarb@v1 + with: + tool-versions: ./cairo/kakarot-ssj/.tool-versions + + - name: Run format check + if: inputs.run-fmt-check + run: cd cairo/kakarot-ssj && scarb fmt --check + + - name: Run tests + run: cd cairo/kakarot-ssj && scarb test diff --git a/.gitignore b/.gitignore index 9dd674473..b7d3baf47 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,13 @@ profile*.png *.memory *.air_private_input.json *.air_public_input.json + +.DS_Store +.idea +node_modules +*.snfoundry_cache +cairo/kakarot-ssj/target +cairo/kakarot-ssj/cache/ +cairo/kakarot-ssj/scripts/libcairo_native_runtime.a +__pycache__ +*.snfoundry diff --git a/.tool-versions b/.tool-versions index 250522192..70e7b8d55 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,4 @@ scarb 0.7.0 scarb 2.6.5 +scarb 2.8.3 +starknet-foundry 0.31.0 diff --git a/.trunk/configs/.codespellrc b/.trunk/configs/.codespellrc new file mode 100644 index 000000000..e4a1d2d22 --- /dev/null +++ b/.trunk/configs/.codespellrc @@ -0,0 +1,5 @@ +[codespell] +ignore-words-list = keypair,astroid,uvloop,crate +skip = '.git' +check-filenames = +check-hidden = diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 14f8bc7c8..3a5423cb0 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -67,9 +67,14 @@ lint: paths: [tests, docker] - linters: [codespell] paths: [uv.lock] + - linters: [cairo] + paths: + - cairo/mock_pragma + - cairo/token + - cairo/utils + - cairo/kakarot-ssj/crates - linters: [ALL] paths: - - cairo1_contracts - logs* - lib* - resources* diff --git a/Makefile b/Makefile index 5e21574ab..49593abcf 100644 --- a/Makefile +++ b/Makefile @@ -6,44 +6,49 @@ endif .PHONY: build test coverage clean -# 176384150 corresponds to release v0.1.13 of Kakarot SSJ. -KKRT_SSJ_RELEASE_ID = 176384150 -# Kakarot SSJ artifacts for precompiles. -KKRT_SSJ_BUILD_ARTIFACT_URL = $(shell curl -L https://api.github.com/repos/kkrt-labs/kakarot-ssj/releases/${KKRT_SSJ_RELEASE_ID} | jq -r '.assets[0].browser_download_url') KATANA_VERSION = v1.0.0-alpha.14 -ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) BUILD_DIR = build SSJ_DIR = $(BUILD_DIR)/ssj -SSJ_ZIP = dev-artifacts.zip -build: $(SSJ_DIR) +build-ssj: + @echo "Building Kakarot SSJ" + @mkdir -p $(SSJ_DIR) + @cd cairo/kakarot-ssj && scarb build -p contracts && find target/dev -type f -name '*contracts*' | grep -vE 'test|mock|Mock' | xargs -I {} cp {} ../../$(SSJ_DIR) + +build: build-ssj uv run compile deploy: build build-sol uv run deploy -$(SSJ_DIR): $(SSJ_ZIP) - rm -rf $(SSJ_DIR) - mkdir -p $(SSJ_DIR) - unzip -o $(SSJ_ZIP) -d $(SSJ_DIR) - rm -f $(SSJ_ZIP) - -$(SSJ_ZIP): - curl -sL -o $(SSJ_ZIP) "$(KKRT_SSJ_BUILD_ARTIFACT_URL)" - fetch-ef-tests: - poetry run python ./kakarot_scripts/ef_tests/fetch.py + uv run ef_tests setup: uv sync --all-extras --dev -test: deploy - uv run pytest tests/src -m "not NoCI" --log-cli-level=INFO -n logical --seed 42 +test-cairo-zero: deploy + uv run pytest cairo_zero/tests/src -m "not NoCI" --log-cli-level=INFO -n logical --seed 42 uv run pytest tests/end_to_end --seed 42 -test-unit: build-sol - uv run pytest tests/src -m "not NoCI" -n logical --seed 42 +test-unit-cairo-zero: build-sol + uv run pytest cairo_zero/tests/src -m "not NoCI" -n logical --seed 42 + +test-unit-cairo: + @PACKAGE="$(word 2,$(MAKECMDGOALS))" && \ + FILTER="$(word 3,$(MAKECMDGOALS))" && cd cairo/kakarot-ssj && \ + if [ -z "$$PACKAGE" ] && [ -z "$$FILTER" ]; then \ + scarb test; \ + elif [ -n "$$PACKAGE" ] && [ -z "$$FILTER" ]; then \ + scarb test -p $$PACKAGE; \ + elif [ -n "$$PACKAGE" ] && [ -n "$$FILTER" ]; then \ + uv run scripts/run_filtered_tests.py scarb test -p $$PACKAGE $$FILTER; \ + else \ + echo "Usage: make test-unit-cairo [PACKAGE] [FILTER]"; \ + exit 1; \ + fi + test-end-to-end: deploy uv run pytest tests/end_to_end --seed 42 diff --git a/audits/Kakarot EVM - Zellic Audit Report.pdf b/audits/cairo_zero/Kakarot EVM - Zellic Audit Report.pdf similarity index 100% rename from audits/Kakarot EVM - Zellic Audit Report.pdf rename to audits/cairo_zero/Kakarot EVM - Zellic Audit Report.pdf diff --git a/cairo/kakarot-ssj/.all-contributorsrc b/cairo/kakarot-ssj/.all-contributorsrc new file mode 100644 index 000000000..5ab9a148a --- /dev/null +++ b/cairo/kakarot-ssj/.all-contributorsrc @@ -0,0 +1,189 @@ +{ + "projectName": "kakarot-ssj", + "projectOwner": "sayajin-labs", + "repoType": "github", + "repoHost": "https://github.com", + "files": ["README.md"], + "imageSize": 100, + "commit": true, + "commitConvention": "gitmoji", + "contributors": [ + { + "login": "abdelhamidbakhta", + "name": "Abdel @ StarkWare ", + "avatar_url": "https://avatars.githubusercontent.com/u/45264458?v=4", + "profile": "https://github.com/abdelhamidbakhta", + "contributions": ["code"] + }, + { + "login": "jobez", + "name": "johann bestowrous", + "avatar_url": "https://avatars.githubusercontent.com/u/615197?v=4", + "profile": "https://github.com/jobez", + "contributions": ["code"] + }, + { + "login": "Eikix", + "name": "Elias Tazartes", + "avatar_url": "https://avatars.githubusercontent.com/u/66871571?v=4", + "profile": "https://github.com/Eikix", + "contributions": ["review", "tutorial", "talk"] + }, + { + "login": "enitrat", + "name": "Mathieu", + "avatar_url": "https://avatars.githubusercontent.com/u/60658558?v=4", + "profile": "https://github.com/enitrat", + "contributions": ["code", "test", "doc"] + }, + { + "login": "khaeljy", + "name": "khaeljy", + "avatar_url": "https://avatars.githubusercontent.com/u/1810456?v=4", + "profile": "https://github.com/khaeljy", + "contributions": ["code"] + }, + { + "login": "ClementWalter", + "name": "ClΓ©ment Walter", + "avatar_url": "https://avatars.githubusercontent.com/u/18620296?v=4", + "profile": "https://www.linkedin.com/in/clementwalter/", + "contributions": ["code"] + }, + { + "login": "LucasLvy", + "name": "Lucas @ StarkWare", + "avatar_url": "https://avatars.githubusercontent.com/u/70894690?v=4", + "profile": "https://github.com/LucasLvy", + "contributions": ["code"] + }, + { + "login": "lambda-0x", + "name": "lambda-0x", + "avatar_url": "https://avatars.githubusercontent.com/u/87354252?v=4", + "profile": "https://github.com/lambda-0x", + "contributions": ["code"] + }, + { + "login": "danilowhk", + "name": "danilowhk", + "avatar_url": "https://avatars.githubusercontent.com/u/12735159?v=4", + "profile": "https://github.com/danilowhk", + "contributions": ["code"] + }, + { + "login": "TAdev0", + "name": "Tristan", + "avatar_url": "https://avatars.githubusercontent.com/u/122918260?v=4", + "profile": "https://github.com/TAdev0", + "contributions": ["code"] + }, + { + "login": "Quentash", + "name": "Quentash", + "avatar_url": "https://avatars.githubusercontent.com/u/100387965?v=4", + "profile": "https://github.com/Quentash", + "contributions": ["code"] + }, + { + "login": "ftupas", + "name": "ftupas", + "avatar_url": "https://avatars.githubusercontent.com/u/35031356?v=4", + "profile": "https://github.com/ftupas", + "contributions": ["code"] + }, + { + "login": "aniketpr01", + "name": "Aniket Prajapati", + "avatar_url": "https://avatars.githubusercontent.com/u/46114123?v=4", + "profile": "https://aniketpr01.github.io/", + "contributions": ["code"] + }, + { + "login": "dbejarano820", + "name": "Daniel Bejarano", + "avatar_url": "https://avatars.githubusercontent.com/u/58019353?v=4", + "profile": "https://github.com/dbejarano820", + "contributions": ["code"] + }, + { + "login": "Noeljarillo", + "name": "Noeljarillo", + "avatar_url": "https://avatars.githubusercontent.com/u/77942794?v=4", + "profile": "https://github.com/Noeljarillo", + "contributions": ["code"] + }, + { + "login": "trbutler4", + "name": "Thomas Butler", + "avatar_url": "https://avatars.githubusercontent.com/u/58192340?v=4", + "profile": "https://github.com/trbutler4", + "contributions": ["code"] + }, + { + "login": "kariy", + "name": "Ammar Arif", + "avatar_url": "https://avatars.githubusercontent.com/u/26515232?v=4", + "profile": "https://github.com/kariy", + "contributions": ["doc"] + }, + { + "login": "greged93", + "name": "greged93", + "avatar_url": "https://avatars.githubusercontent.com/u/82421016?v=4", + "profile": "https://github.com/greged93", + "contributions": ["code"] + }, + { + "login": "chachaleo", + "name": "Charlotte", + "avatar_url": "https://avatars.githubusercontent.com/u/49371958?v=4", + "profile": "https://github.com/chachaleo", + "contributions": ["code"] + }, + { + "login": "akhercha", + "name": "akhercha", + "avatar_url": "https://avatars.githubusercontent.com/u/22559023?v=4", + "profile": "https://t.me/notaihe", + "contributions": ["code"] + }, + { + "login": "alextnetto", + "name": "Alexandro T. Netto", + "avatar_url": "https://avatars.githubusercontent.com/u/56097505?v=4", + "profile": "https://github.com/alextnetto", + "contributions": ["code"] + }, + { + "login": "edisontim", + "name": "tedison", + "avatar_url": "https://avatars.githubusercontent.com/u/76473430?v=4", + "profile": "https://github.com/edisontim", + "contributions": ["code"] + }, + { + "login": "rkdud007", + "name": "Pia", + "avatar_url": "https://avatars.githubusercontent.com/u/76558220?v=4", + "profile": "https://github.com/rkdud007", + "contributions": ["code"] + }, + { + "login": "glihm", + "name": "glihm", + "avatar_url": "https://avatars.githubusercontent.com/u/7962849?v=4", + "profile": "https://github.com/glihm", + "contributions": ["code"] + }, + { + "login": "bajpai244", + "name": "Harsh Bajpai", + "avatar_url": "https://avatars.githubusercontent.com/u/41180869?v=4", + "profile": "https://github.com/bajpai244", + "contributions": ["code"] + } + ], + "contributorsPerLine": 7, + "linkToUsage": true +} diff --git a/cairo/kakarot-ssj/.tool-versions b/cairo/kakarot-ssj/.tool-versions new file mode 100644 index 000000000..5248f18a0 --- /dev/null +++ b/cairo/kakarot-ssj/.tool-versions @@ -0,0 +1,2 @@ +scarb 2.8.3 +starknet-foundry 0.31.0 diff --git a/cairo/kakarot-ssj/README.md b/cairo/kakarot-ssj/README.md new file mode 100644 index 000000000..196587351 --- /dev/null +++ b/cairo/kakarot-ssj/README.md @@ -0,0 +1,247 @@ +

+ +

+
+

+ Kakarot, the zkEVM written in Cairo. +

+
+ +
+
+ +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/kkrt-labs/kakarot-ssj/test.yml?branch=main) +![GitHub](https://img.shields.io/github/license/kkrt-labs/kakarot-ssj?style=flat-square&logo=github) +![GitHub contributors](https://img.shields.io/github/contributors/kkrt-labs/kakarot-ssj?logo=github&style=flat-square) +![GitHub top language](https://img.shields.io/github/languages/top/kkrt-labs/kakarot-ssj?style=flat-square) +[![Telegram](https://img.shields.io/badge/telegram-Kakarot-yellow.svg?logo=telegram)](https://t.me/KakarotZkEvm) +![Contributions welcome](https://img.shields.io/badge/contributions-welcome-orange.svg) +[![Read FAQ](https://img.shields.io/badge/Ask%20Question-Read%20FAQ-000000)](https://www.newton.so/view?tags=kakarot) +![GitHub Repo stars](https://img.shields.io/github/stars/kkrt-labs/kakarot-ssj?style=social) +[![Twitter Follow](https://img.shields.io/twitter/follow/KakarotZkEvm?style=social)](https://x.com/KakarotZkEvm) + +
+ +
+Table of Contents + +- [About](#about) +- [Getting Started](#getting-started) + - [Installation](#installation) +- [Usage](#usage) + - [Build](#build) + - [Test](#test) + - [Format](#format) +- [Roadmap](#roadmap) +- [Support](#support) +- [Project assistance](#project-assistance) +- [Contributing](#contributing) +- [Authors \& contributors](#authors--contributors) +- [Security](#security) +- [License](#license) +- [Acknowledgements](#acknowledgements) +- [Contributors ✨](#contributors-) + +
+ +--- + +## About + +Kakarot is an (zk)-Ethereum Virtual Machine implementation written in Cairo. +Kakarot is Ethereum compatible, i.e. all existing smart contracts, developer +tools and wallets work out-of-the-box on Kakarot. It's been open source from day +one. Soon available on Starknet L2 and Appchains. + +🚧 It is a work in progress, and it is not ready for production. + +## Getting Started + +This repository is a Cairo rewrite of +[the CairoZero version of Kakarot zkEVM](https://github.com/kkrt-labs/kakarot). + +### Installation + +- Install [Scarb](https://docs.swmansion.com/scarb). To make sure your version + always matches the one used by Kakarot, you can install Scarb + [via asdf](https://docs.swmansion.com/scarb/download#install-via-asdf). + +- Install + [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html?highlight=asdf#installation-via-asdf). + We also recommend installing it via asdf. + +- Install the + [Cairo Profiler](https://github.com/software-mansion/cairo-profiler) to + profile your Cairo code. + +- Install [Bun](https://bun.sh/docs/installation) to run the JavaScript scripts. + +- [Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the + repository and clone your fork + (`git clone https://github.com//kakarot-ssj`) + +- Run `make install` to install the git hooks. +- Add your environment variables to `.env` (see `.env.example` for an example). + - Get your Github token [here](https://github.com/settings/tokens?type=beta) + +## Usage + +### Build + +```bash +scarb build +``` + +### Test + +```bash +scarb test +``` + +### Format + +The project uses [trunk](https://trunk.io/) for everything except cairo files. +If you don't have it installed already, you can do: + +```bash +curl https://get.trunk.io -fsSL | bash +``` + +then + +```bash +trunk check --fix +``` + +VS Code users, don't miss the +[VS Code trunk plugin](https://marketplace.visualstudio.com/items?itemName=Trunk.io). + +For cairo files, run: + +```bash +scarb fmt +``` + +## Roadmap + +See the [open issues](https://github.com/kkrt-labs/kakarot-ssj/issues) for a +list of proposed features (and known issues). + +- [Top Feature Requests](https://github.com/kkrt-labs/kakarot-ssj/issues?q=label%3Aenhancement+is%3Aopen+sort%3Areactions-%2B1-desc) + (Add your votes using the πŸ‘ reaction) +- [Top Bugs](https://github.com/kkrt-labs/kakarot-ssj/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) + (Add your votes using the πŸ‘ reaction) +- [Newest Bugs](https://github.com/kkrt-labs/kakarot-ssj/issues?q=is%3Aopen+is%3Aissue+label%3Abug) + +## Support + +Reach out to the maintainer at one of the following places: + +- [GitHub Discussions](https://github.com/kkrt-labs/kakarot-ssj/discussions) +- [Telegram group](https://t.me/KakarotZkEvm) + +## Project assistance + +If you want to say **thank you** or/and support active development of Kakarot: + +- Add a [GitHub Star](https://github.com/kkrt-labs/kakarot-ssj) to the project. +- Tweet about [Kakarot](https://twitter.com/KakarotZkEvm). +- Write interesting articles about the project on [Dev.to](https://dev.to/), + [Medium](https://medium.com/), [Mirror](https://mirror.xyz/) or your personal + blog. + +Together, we can make Kakarot **better**! + +## Contributing + +First off, thanks for taking the time to contribute! Contributions are what make +the open-source community such an amazing place to learn, inspire, and create. +Any contribution you make will benefit everybody else and is **greatly +appreciated**. + +Please read [our contribution guidelines](docs/CONTRIBUTING.md), and thank you +for being involved! + +## Authors & contributors + +For a full list of all authors and contributors, see +[the contributors page](https://github.com/kkrt-labs/kakarot-ssj/contributors). + +## Security + +Kakarot follows good practices of security, but 100% security cannot be assured. +Kakarot is provided **"as is"** without any **warranty**. Use at your own risk. + +_For more information and to report security issues, please refer to our +[security documentation](docs/SECURITY.md)._ + +## License + +This project is licensed under the **MIT license**. + +See [LICENSE](LICENSE) for more information. + +## Acknowledgements + +## Contributors ✨ + +Thanks goes to these wonderful people +([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Abdel @ StarkWare
Abdel @ StarkWare

πŸ’»
johann bestowrous
johann bestowrous

πŸ’»
Elias Tazartes
Elias Tazartes

πŸ‘€ βœ… πŸ“’
Mathieu
Mathieu

πŸ’» ⚠️ πŸ“–
khaeljy
khaeljy

πŸ’»
ClΓ©ment Walter
ClΓ©ment Walter

πŸ’»
Lucas @ StarkWare
Lucas @ StarkWare

πŸ’»
lambda-0x
lambda-0x

πŸ’»
danilowhk
danilowhk

πŸ’»
Tristan
Tristan

πŸ’»
Quentash
Quentash

πŸ’»
ftupas
ftupas

πŸ’»
Aniket Prajapati
Aniket Prajapati

πŸ’»
Daniel Bejarano
Daniel Bejarano

πŸ’»
Noeljarillo
Noeljarillo

πŸ’»
Thomas Butler
Thomas Butler

πŸ’»
Ammar Arif
Ammar Arif

πŸ“–
greged93
greged93

πŸ’»
Charlotte
Charlotte

πŸ’»
akhercha
akhercha

πŸ’»
Alexandro T. Netto
Alexandro T. Netto

πŸ’»
tedison
tedison

πŸ’»
Pia
Pia

πŸ’»
glihm
glihm

πŸ’»
+ + Add your contributions + +
+ + + + + + +This project follows the +[all-contributors](https://github.com/all-contributors/all-contributors) +specification. Contributions of any kind welcome! diff --git a/cairo/kakarot-ssj/Scarb.lock b/cairo/kakarot-ssj/Scarb.lock new file mode 100644 index 000000000..8a345708d --- /dev/null +++ b/cairo/kakarot-ssj/Scarb.lock @@ -0,0 +1,74 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "alexandria_data_structures" +version = "0.1.0" +dependencies = [ + "snforge_std", +] + +[[package]] +name = "contracts" +version = "0.1.0" +dependencies = [ + "evm", + "openzeppelin", + "snforge_std", + "snforge_utils", + "utils", +] + +[[package]] +name = "evm" +version = "0.1.0" +dependencies = [ + "contracts", + "garaga", + "openzeppelin", + "snforge_std", + "snforge_utils", + "utils", +] + +[[package]] +name = "garaga" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/garaga.git#933784eee3811334cf1a5b83d9ffcc9cfda3be8c" + +[[package]] +name = "openzeppelin" +version = "0.1.0" +dependencies = [ + "snforge_std", +] + +[[package]] +name = "snforge_scarb_plugin" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#249f1e9960b7ee07d80e12b585ac57b644f9e4c0" + +[[package]] +name = "snforge_std" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#249f1e9960b7ee07d80e12b585ac57b644f9e4c0" +dependencies = [ + "snforge_scarb_plugin", +] + +[[package]] +name = "snforge_utils" +version = "0.1.0" +dependencies = [ + "evm", + "snforge_std", +] + +[[package]] +name = "utils" +version = "0.1.0" +dependencies = [ + "alexandria_data_structures", + "evm", + "snforge_std", +] diff --git a/cairo/kakarot-ssj/Scarb.toml b/cairo/kakarot-ssj/Scarb.toml new file mode 100644 index 000000000..b0d420965 --- /dev/null +++ b/cairo/kakarot-ssj/Scarb.toml @@ -0,0 +1,17 @@ +[workspace] +members = ["crates/*"] + +[workspace.package] +description = "Kakarot is an (zk)-Ethereum Virtual Machine implementation written in Cairo." +documentation = "https://www.kakarot.org/" +cairo-version = "2.8.2" +version = "0.1.0" +readme = "README.md" +repository = "https://github.com/kkrt-labs/kakarot-ssj/" +license-file = "LICENSE" + +[workspace.dependencies] +starknet = "2.8.2" + +[workspace.tool.fmt] +sort-module-level-items = true diff --git a/cairo/kakarot-ssj/blockchain-tests-skip.yml b/cairo/kakarot-ssj/blockchain-tests-skip.yml new file mode 100644 index 000000000..6d06e0129 --- /dev/null +++ b/cairo/kakarot-ssj/blockchain-tests-skip.yml @@ -0,0 +1,913 @@ +# List of test directories to completely skip +# trunk-ignore(yamllint/empty-values) +directories: + +# List of file names to be skipped +# The first level corresponds to the directory, the second to the list of file names to ignore. +# trunk-ignore(yamllint/empty-values) +filename: + +# Keeping some skipped tests because of a probable memory leak somewhere +# causing cargo test to just crash at some point +regex: + eip4895_withdrawals: + - .* + eip4844_blobs: + - .* + eip4788_beacon_root: + - .* + stEIP4844-blobtransactions: + - .* + +testname: + stEIP1153-transientStorage: + - transStorageOK_d3g0v0_Cancun + - 15_tstoreCannotBeDosd_d0g0v0_Cancun + - 21_tstoreCannotBeDosdOOO_d0g0v0_Cancun + eip198_modexp_precompile: + - modexp__fork_Cancun_minus_blockchain_test_minus_EIP_minus_198_minus_case3_minus_raw_minus_input_minus_out_minus_of_minus_gas + - modexp__fork_Cancun_minus_blockchain_test_minus_EIP_minus_198_minus_case1 + - modexp__fork_Cancun_minus_blockchain_test_minus_EIP_minus_198_minus_case2 + - modexp__fork_Cancun_minus_blockchain_test_minus_EIP_minus_198_minus_case4_minus_extra_minus_data_07 + - modexp__fork_Cancun_minus_blockchain_test_minus_EIP_minus_198_minus_case5_minus_raw_minus_input + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base_00_minus_exponent_00_minus_modulus_02_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x01 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base_01_minus_exponent_01_minus_modulus_02_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x01 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base_02_minus_exponent_01_minus_modulus_03_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x02 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base_02_minus_exponent_02_minus_modulus_05_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x04 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base__minus_exponent_01_minus_modulus_02_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x00 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base__minus_exponent__minus_modulus_0001_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x0000 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base__minus_exponent__minus_modulus_0002_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x0001 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base__minus_exponent__minus_modulus_00_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x00 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base__minus_exponent__minus_modulus_01_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x00 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base__minus_exponent__minus_modulus_02_minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x01 + - modexp__fork_Cancun_minus_blockchain_test_minus_ModExpInput_base__minus_exponent__minus_modulus__minus_ExpectedOutput_call_return_code_0x01_minus_returned_data_0x + initcode: + - contract_creating_tx__fork_Cancun_minus_blockchain_test_minus_over_limit_ones + - contract_creating_tx__fork_Cancun_minus_blockchain_test_minus_over_limit_zeros + stEIP3651-warmcoinbase: + - coinbaseWarmAccountCallGasFail_d0g0v0_Cancun + vmIOandFlowOperations: + - jump_d5g0v0_Cancun + - jumpi_d9g0v0_Cancun + vmLogTest: + - log0_d0g0v0_Cancun + - log1_d0g0v0_Cancun + - log2_d0g0v0_Cancun + - log3_d0g0v0_Cancun + - log4_d0g0v0_Cancun + vmPerformance: + - loopExp_d2g0v0_Cancun + - performanceTester_d0g0v0_Cancun + - performanceTester_d2g0v0_Cancun + - performanceTester_d3g0v0_Cancun + - loopExp_d10g0v0_Cancun + - loopExp_d11g0v0_Cancun + - loopExp_d12g0v0_Cancun + - loopExp_d13g0v0_Cancun + - loopExp_d14g0v0_Cancun + - loopExp_d8g0v0_Cancun + - loopExp_d9g0v0_Cancun + - loopMul_d0g0v0_Cancun + - loopMul_d1g0v0_Cancun + - loopMul_d2g0v0_Cancun + - performanceTester_d1g0v0_Cancun + - performanceTester_d4g0v0_Cancun + vmTests: + - blockInfo_d2g0v0_Cancun + stBadOpcode: + - invalidDiffPlaces_d34g0v0_Cancun + - opc0CDiffPlaces_d34g0v0_Cancun + - opc0DDiffPlaces_d34g0v0_Cancun + - opc0EDiffPlaces_d34g0v0_Cancun + - opc0FDiffPlaces_d34g0v0_Cancun + - opc1EDiffPlaces_d34g0v0_Cancun + - opc1FDiffPlaces_d34g0v0_Cancun + - opc21DiffPlaces_d34g0v0_Cancun + - opc22DiffPlaces_d34g0v0_Cancun + - opc23DiffPlaces_d34g0v0_Cancun + - opc24DiffPlaces_d34g0v0_Cancun + - opc25DiffPlaces_d34g0v0_Cancun + - opc26DiffPlaces_d34g0v0_Cancun + - opc27DiffPlaces_d34g0v0_Cancun + - opc28DiffPlaces_d34g0v0_Cancun + - opc29DiffPlaces_d34g0v0_Cancun + - opc2ADiffPlaces_d34g0v0_Cancun + - opc2BDiffPlaces_d34g0v0_Cancun + - opc2CDiffPlaces_d34g0v0_Cancun + - opc2DDiffPlaces_d34g0v0_Cancun + - opc2EDiffPlaces_d34g0v0_Cancun + - opc2FDiffPlaces_d34g0v0_Cancun + - opc49DiffPlaces_d34g0v0_Cancun + - opc4ADiffPlaces_d34g0v0_Cancun + - opc4BDiffPlaces_d34g0v0_Cancun + - opc4CDiffPlaces_d34g0v0_Cancun + - opc4DDiffPlaces_d34g0v0_Cancun + - opc4EDiffPlaces_d34g0v0_Cancun + - opc4FDiffPlaces_d34g0v0_Cancun + - opc5CDiffPlaces_d34g0v0_Cancun + - opc5DDiffPlaces_d34g0v0_Cancun + - opc5EDiffPlaces_d34g0v0_Cancun + - opc5FDiffPlaces_d34g0v0_Cancun + - opcA5DiffPlaces_d34g0v0_Cancun + - opcA6DiffPlaces_d34g0v0_Cancun + - opcA7DiffPlaces_d34g0v0_Cancun + - opcA8DiffPlaces_d34g0v0_Cancun + - opcA9DiffPlaces_d34g0v0_Cancun + - opcAADiffPlaces_d34g0v0_Cancun + - opcABDiffPlaces_d34g0v0_Cancun + - opcACDiffPlaces_d34g0v0_Cancun + - opcADDiffPlaces_d34g0v0_Cancun + - opcAEDiffPlaces_d34g0v0_Cancun + - opcAFDiffPlaces_d34g0v0_Cancun + - opcB0DiffPlaces_d34g0v0_Cancun + - opcB1DiffPlaces_d34g0v0_Cancun + - opcB2DiffPlaces_d34g0v0_Cancun + - opcB3DiffPlaces_d34g0v0_Cancun + - opcB4DiffPlaces_d34g0v0_Cancun + - opcB5DiffPlaces_d34g0v0_Cancun + - opcB6DiffPlaces_d34g0v0_Cancun + - opcB7DiffPlaces_d34g0v0_Cancun + - opcB8DiffPlaces_d34g0v0_Cancun + - opcB9DiffPlaces_d34g0v0_Cancun + - opcBADiffPlaces_d34g0v0_Cancun + - opcBBDiffPlaces_d34g0v0_Cancun + - opcBCDiffPlaces_d34g0v0_Cancun + - opcBDDiffPlaces_d34g0v0_Cancun + - opcBEDiffPlaces_d34g0v0_Cancun + - opcBFDiffPlaces_d34g0v0_Cancun + - opcC0DiffPlaces_d34g0v0_Cancun + - opcC1DiffPlaces_d34g0v0_Cancun + - opcC2DiffPlaces_d34g0v0_Cancun + - opcC3DiffPlaces_d34g0v0_Cancun + - opcC4DiffPlaces_d34g0v0_Cancun + - opcC5DiffPlaces_d34g0v0_Cancun + - opcC6DiffPlaces_d34g0v0_Cancun + - opcC7DiffPlaces_d34g0v0_Cancun + - opcC8DiffPlaces_d34g0v0_Cancun + - opcC9DiffPlaces_d34g0v0_Cancun + - opcCADiffPlaces_d34g0v0_Cancun + - opcCBDiffPlaces_d34g0v0_Cancun + - opcCCDiffPlaces_d34g0v0_Cancun + - opcCDDiffPlaces_d34g0v0_Cancun + - opcCEDiffPlaces_d34g0v0_Cancun + - opcCFDiffPlaces_d34g0v0_Cancun + - opcD0DiffPlaces_d34g0v0_Cancun + - opcD1DiffPlaces_d34g0v0_Cancun + - opcD2DiffPlaces_d34g0v0_Cancun + - opcD3DiffPlaces_d34g0v0_Cancun + - opcD4DiffPlaces_d34g0v0_Cancun + - opcD5DiffPlaces_d34g0v0_Cancun + - opcD6DiffPlaces_d34g0v0_Cancun + - opcD7DiffPlaces_d34g0v0_Cancun + - opcD8DiffPlaces_d34g0v0_Cancun + - opcD9DiffPlaces_d34g0v0_Cancun + - opcDADiffPlaces_d34g0v0_Cancun + - opcDBDiffPlaces_d34g0v0_Cancun + - opcDCDiffPlaces_d34g0v0_Cancun + - opcDDDiffPlaces_d34g0v0_Cancun + - opcDEDiffPlaces_d34g0v0_Cancun + - opcDFDiffPlaces_d34g0v0_Cancun + - opcE0DiffPlaces_d34g0v0_Cancun + - opcE1DiffPlaces_d34g0v0_Cancun + - opcE2DiffPlaces_d34g0v0_Cancun + - opcE3DiffPlaces_d34g0v0_Cancun + - opcE4DiffPlaces_d34g0v0_Cancun + - opcE5DiffPlaces_d34g0v0_Cancun + - opcE6DiffPlaces_d34g0v0_Cancun + - opcE7DiffPlaces_d34g0v0_Cancun + - opcE8DiffPlaces_d34g0v0_Cancun + - opcE9DiffPlaces_d34g0v0_Cancun + - opcEADiffPlaces_d34g0v0_Cancun + - opcEBDiffPlaces_d34g0v0_Cancun + - opcECDiffPlaces_d34g0v0_Cancun + - opcEDDiffPlaces_d34g0v0_Cancun + - opcEEDiffPlaces_d34g0v0_Cancun + - opcEFDiffPlaces_d34g0v0_Cancun + - opcF6DiffPlaces_d34g0v0_Cancun + - opcF7DiffPlaces_d34g0v0_Cancun + - opcF8DiffPlaces_d34g0v0_Cancun + - opcF9DiffPlaces_d34g0v0_Cancun + - opcFBDiffPlaces_d34g0v0_Cancun + - opcFCDiffPlaces_d34g0v0_Cancun + - opcFEDiffPlaces_d34g0v0_Cancun + - operationDiffGas_d0g0v0_Cancun + - operationDiffGas_d1g0v0_Cancun + stEIP3860-limitmeterinitcode: + - creationTxInitCodeSizeLimit_d1g0v0_Cancun + stCallCreateCallCodeTest: + - Call1024BalanceTooLow_d0g0v0_Cancun + - CallRecursiveBombPreCall_d0g0v0_Cancun + - Callcode1024BalanceTooLow_d0g0v0_Cancun + - createInitFailStackSizeLargerThan1024_d0g0v0_Cancun + - Call1024PreCalls_d0g0v0_Cancun + - Call1024PreCalls_d0g1v0_Cancun + stCreate2: + - Create2OnDepth1023_d0g0v0_Cancun + - Create2OnDepth1024_d0g0v0_Cancun + - create2callPrecompiles_d2g0v0_Cancun + - RevertInCreateInInitCreate2Paris_d0g0v0_Cancun + - create2collisionStorageParis_d0g0v0_Cancun + - create2collisionStorageParis_d1g0v0_Cancun + - create2collisionStorageParis_d2g0v0_Cancun + stCreateTest: + - CreateOOGafterMaxCodesize_d2g0v0_Cancun + - CreateOOGafterMaxCodesize_d3g0v0_Cancun + - CreateOOGafterMaxCodesize_d5g0v0_Cancun + stEIP150Specific: + - Transaction64Rule_integerBoundaries_d0g0v0_Cancun + - Transaction64Rule_integerBoundaries_d10g0v0_Cancun + - Transaction64Rule_integerBoundaries_d11g0v0_Cancun + - Transaction64Rule_integerBoundaries_d1g0v0_Cancun + - Transaction64Rule_integerBoundaries_d2g0v0_Cancun + - Transaction64Rule_integerBoundaries_d3g0v0_Cancun + - Transaction64Rule_integerBoundaries_d4g0v0_Cancun + - Transaction64Rule_integerBoundaries_d5g0v0_Cancun + - Transaction64Rule_integerBoundaries_d6g0v0_Cancun + - Transaction64Rule_integerBoundaries_d7g0v0_Cancun + - Transaction64Rule_integerBoundaries_d8g0v0_Cancun + - Transaction64Rule_integerBoundaries_d9g0v0_Cancun + stDelegatecallTestHomestead: + - Call1024BalanceTooLow_d0g0v0_Cancun + - CallRecursiveBombPreCall_d0g0v0_Cancun + - Delegatecall1024_d0g0v0_Cancun + - Call1024PreCalls_d0g0v0_Cancun + - Call1024PreCalls_d0g1v0_Cancun + - Call1024PreCalls_d0g2v0_Cancun + stEIP150singleCodeGasPrices: + - gasCostJump_d0g0v0_Cancun + - gasCostJump_d1g0v0_Cancun + - gasCostJump_d2g0v0_Cancun + - gasCostReturn_d0g0v0_Cancun + stEIP1559: + - baseFeeDiffPlaces_d34g0v0_Cancun + - gasPriceDiffPlaces_d34g0v0_Cancun + stExtCodeHash: + - extCodeHashDynamicArgument_d1g0v0_Cancun + - dynamicAccountOverwriteEmpty_Paris_d0g0v0_Cancun + stMemoryTest: + - stackLimitGas_1023_d0g0v0_Cancun + - stackLimitGas_1024_d0g0v0_Cancun + - stackLimitGas_1025_d0g0v0_Cancun + - stackLimitPush31_1023_d0g0v0_Cancun + - stackLimitPush31_1024_d0g0v0_Cancun + - stackLimitPush31_1025_d0g0v0_Cancun + - stackLimitPush32_1023_d0g0v0_Cancun + - stackLimitPush32_1024_d0g0v0_Cancun + - stackLimitPush32_1025_d0g0v0_Cancun + stPreCompiledContracts2: + - CALLCODERipemd160_0_d0g0v0_Cancun + - CALLCODERipemd160_1_d0g0v0_Cancun + - CALLCODERipemd160_2_d0g0v0_Cancun + - CALLCODERipemd160_3_d0g0v0_Cancun + - CALLCODERipemd160_3_postfixed0_d0g0v0_Cancun + - CALLCODERipemd160_3_prefixed0_d0g0v0_Cancun + - CALLCODERipemd160_4_d0g0v0_Cancun + - CALLCODERipemd160_4_gas719_d0g0v0_Cancun + - CallRipemd160_0_d0g0v0_Cancun + - CallRipemd160_1_d0g0v0_Cancun + - CallRipemd160_2_d0g0v0_Cancun + - CallRipemd160_3_d0g0v0_Cancun + - CallRipemd160_3_postfixed0_d0g0v0_Cancun + - CallRipemd160_3_prefixed0_d0g0v0_Cancun + - CallRipemd160_4_d0g0v0_Cancun + - CallRipemd160_4_gas719_d0g0v0_Cancun + - ecrecoverShortBuff_d0g0v0_Cancun + - modexpRandomInput_d2g0v0_Cancun + - modexpRandomInput_d2g1v0_Cancun + - CALLBlake2f_d9g0v0_Cancun + - CALLBlake2f_d9g1v0_Cancun + - CALLBlake2f_d9g2v0_Cancun + - CALLBlake2f_d9g3v0_Cancun + - CALLCODEBlake2f_d9g0v0_Cancun + - CALLCODEBlake2f_d9g1v0_Cancun + - CALLCODEBlake2f_d9g2v0_Cancun + - CALLCODEBlake2f_d9g3v0_Cancun + - CALLCODERipemd160_5_d0g0v0_Cancun + - CallEcrecover_Overflow_d0g0v0_Cancun + - CallEcrecover_Overflow_d4g0v0_Cancun + - CallEcrecover_Overflow_d5g0v0_Cancun + - CallRipemd160_5_d0g0v0_Cancun + stPreCompiledContracts: + - idPrecomps_d2g0v0_Cancun + - idPrecomps_d7g0v0_Cancun + - idPrecomps_d9g0v0_Cancun + - precompsEIP2929Cancun_d110g0v0_Cancun + - precompsEIP2929Cancun_d117g0v0_Cancun + - precompsEIP2929Cancun_d128g0v0_Cancun + - precompsEIP2929Cancun_d12g0v0_Cancun + - precompsEIP2929Cancun_d135g0v0_Cancun + - precompsEIP2929Cancun_d146g0v0_Cancun + - precompsEIP2929Cancun_d151g0v0_Cancun + - precompsEIP2929Cancun_d153g0v0_Cancun + - precompsEIP2929Cancun_d164g0v0_Cancun + - precompsEIP2929Cancun_d169g0v0_Cancun + - precompsEIP2929Cancun_d171g0v0_Cancun + - precompsEIP2929Cancun_d182g0v0_Cancun + - precompsEIP2929Cancun_d189g0v0_Cancun + - precompsEIP2929Cancun_d200g0v0_Cancun + - precompsEIP2929Cancun_d207g0v0_Cancun + - precompsEIP2929Cancun_d20g0v0_Cancun + - precompsEIP2929Cancun_d218g0v0_Cancun + - precompsEIP2929Cancun_d225g0v0_Cancun + - precompsEIP2929Cancun_d236g0v0_Cancun + - precompsEIP2929Cancun_d241g0v0_Cancun + - precompsEIP2929Cancun_d243g0v0_Cancun + - precompsEIP2929Cancun_d254g0v0_Cancun + - precompsEIP2929Cancun_d261g0v0_Cancun + - precompsEIP2929Cancun_d272g0v0_Cancun + - precompsEIP2929Cancun_d279g0v0_Cancun + - precompsEIP2929Cancun_d27g0v0_Cancun + - precompsEIP2929Cancun_d290g0v0_Cancun + - precompsEIP2929Cancun_d295g0v0_Cancun + - precompsEIP2929Cancun_d297g0v0_Cancun + - precompsEIP2929Cancun_d308g0v0_Cancun + - precompsEIP2929Cancun_d315g0v0_Cancun + - precompsEIP2929Cancun_d38g0v0_Cancun + - precompsEIP2929Cancun_d43g0v0_Cancun + - precompsEIP2929Cancun_d45g0v0_Cancun + - precompsEIP2929Cancun_d56g0v0_Cancun + - precompsEIP2929Cancun_d5g0v0_Cancun + - precompsEIP2929Cancun_d61g0v0_Cancun + - precompsEIP2929Cancun_d63g0v0_Cancun + - precompsEIP2929Cancun_d74g0v0_Cancun + - precompsEIP2929Cancun_d81g0v0_Cancun + - precompsEIP2929Cancun_d92g0v0_Cancun + - precompsEIP2929Cancun_d99g0v0_Cancun + - modexp_d11g0v0_Cancun + - modexp_d11g1v0_Cancun + - modexp_d11g2v0_Cancun + - modexp_d11g3v0_Cancun + - modexp_d14g0v0_Cancun + - modexp_d14g1v0_Cancun + - modexp_d14g2v0_Cancun + - modexp_d14g3v0_Cancun + - modexp_d16g0v0_Cancun + - modexp_d16g1v0_Cancun + - modexp_d16g2v0_Cancun + - modexp_d16g3v0_Cancun + - modexp_d17g0v0_Cancun + - modexp_d17g1v0_Cancun + - modexp_d17g2v0_Cancun + - modexp_d17g3v0_Cancun + - modexp_d25g0v0_Cancun + - modexp_d25g1v0_Cancun + - modexp_d25g2v0_Cancun + - modexp_d25g3v0_Cancun + - modexp_d26g0v0_Cancun + - modexp_d26g1v0_Cancun + - modexp_d26g2v0_Cancun + - modexp_d26g3v0_Cancun + - modexp_d27g0v0_Cancun + - modexp_d27g1v0_Cancun + - modexp_d27g2v0_Cancun + - modexp_d27g3v0_Cancun + - modexp_d29g0v0_Cancun + - modexp_d29g1v0_Cancun + - modexp_d29g2v0_Cancun + - modexp_d29g3v0_Cancun + - modexp_d30g0v0_Cancun + - modexp_d30g1v0_Cancun + - modexp_d30g2v0_Cancun + - modexp_d30g3v0_Cancun + - modexp_d33g0v0_Cancun + - modexp_d33g1v0_Cancun + - modexp_d33g2v0_Cancun + - modexp_d33g3v0_Cancun + - precompsEIP2929Cancun_d10g0v0_Cancun + - precompsEIP2929Cancun_d115g0v0_Cancun + - precompsEIP2929Cancun_d133g0v0_Cancun + - precompsEIP2929Cancun_d187g0v0_Cancun + - precompsEIP2929Cancun_d205g0v0_Cancun + - precompsEIP2929Cancun_d223g0v0_Cancun + - precompsEIP2929Cancun_d259g0v0_Cancun + - precompsEIP2929Cancun_d25g0v0_Cancun + - precompsEIP2929Cancun_d277g0v0_Cancun + - precompsEIP2929Cancun_d313g0v0_Cancun + - precompsEIP2929Cancun_d79g0v0_Cancun + - precompsEIP2929Cancun_d97g0v0_Cancun + stQuadraticComplexityTest: + - Call20KbytesContract50_1_d0g0v0_Cancun + - Call20KbytesContract50_2_d0g0v0_Cancun + - Call20KbytesContract50_3_d0g0v0_Cancun + - Call20KbytesContract50_3_d0g1v0_Cancun + - Call1MB1024Calldepth_d0g1v0_Cancun + - Call20KbytesContract50_1_d0g1v0_Cancun + - Call20KbytesContract50_2_d0g1v0_Cancun + - Call50000_d0g1v0_Cancun + - Call50000_ecrec_d0g1v0_Cancun + - Call50000_identity2_d0g1v0_Cancun + - Call50000_identity_d0g1v0_Cancun + - Call50000_rip160_d0g0v0_Cancun + - Call50000_rip160_d0g1v0_Cancun + - Call50000_sha256_d0g1v0_Cancun + - Callcode50000_d0g1v0_Cancun + - QuadraticComplexitySolidity_CallDataCopy_d0g1v0_Cancun + - Return50000_2_d0g0v0_Cancun + - Return50000_2_d0g1v0_Cancun + - Return50000_d0g0v0_Cancun + - Return50000_d0g1v0_Cancun + stRandom2: + - randomStatetest415_d0g0v0_Cancun + - randomStatetest418_d0g0v0_Cancun + - randomStatetest433_d0g0v0_Cancun + - randomStatetest458_d0g0v0_Cancun + - randomStatetest467_d0g0v0_Cancun + - randomStatetest469_d0g0v0_Cancun + - randomStatetest547_d0g0v0_Cancun + - randomStatetest554_d0g0v0_Cancun + - randomStatetest560_d0g0v0_Cancun + - randomStatetest583_d0g0v0_Cancun + - randomStatetest636_d0g0v0_Cancun + - randomStatetest639_d0g0v0_Cancun + - randomStatetest645_d0g0v0_Cancun + - randomStatetest645_d0g0v1_Cancun + - randomStatetest476_d0g0v0_Cancun + stRandom: + - randomStatetest111_d0g0v0_Cancun + - randomStatetest14_d0g0v0_Cancun + - randomStatetest150_d0g0v0_Cancun + - randomStatetest154_d0g0v0_Cancun + - randomStatetest178_d0g0v0_Cancun + - randomStatetest211_d0g0v0_Cancun + - randomStatetest260_d0g0v0_Cancun + - randomStatetest306_d0g0v0_Cancun + - randomStatetest159_d0g0v0_Cancun + - randomStatetest163_d0g0v0_Cancun + - randomStatetest177_d0g0v0_Cancun + - randomStatetest185_d0g0v0_Cancun + - randomStatetest326_d0g0v0_Cancun + - randomStatetest36_d0g0v0_Cancun + - randomStatetest384_d0g0v0_Cancun + - randomStatetest48_d0g0v0_Cancun + stReturnDataTest: + - revertRetDataSize_d0g0v0_Cancun + - revertRetDataSize_d10g0v0_Cancun + - revertRetDataSize_d11g0v0_Cancun + - revertRetDataSize_d12g0v0_Cancun + - revertRetDataSize_d13g0v0_Cancun + - revertRetDataSize_d14g0v0_Cancun + - revertRetDataSize_d15g0v0_Cancun + - revertRetDataSize_d16g0v0_Cancun + - revertRetDataSize_d17g0v0_Cancun + - revertRetDataSize_d18g0v0_Cancun + - revertRetDataSize_d19g0v0_Cancun + - revertRetDataSize_d1g0v0_Cancun + - revertRetDataSize_d20g0v0_Cancun + - revertRetDataSize_d21g0v0_Cancun + - revertRetDataSize_d22g0v0_Cancun + - revertRetDataSize_d23g0v0_Cancun + - revertRetDataSize_d24g0v0_Cancun + - revertRetDataSize_d25g0v0_Cancun + - revertRetDataSize_d26g0v0_Cancun + - revertRetDataSize_d27g0v0_Cancun + - revertRetDataSize_d28g0v0_Cancun + - revertRetDataSize_d29g0v0_Cancun + - revertRetDataSize_d2g0v0_Cancun + - revertRetDataSize_d30g0v0_Cancun + - revertRetDataSize_d31g0v0_Cancun + - revertRetDataSize_d32g0v0_Cancun + - revertRetDataSize_d33g0v0_Cancun + - revertRetDataSize_d34g0v0_Cancun + - revertRetDataSize_d35g0v0_Cancun + - revertRetDataSize_d3g0v0_Cancun + - revertRetDataSize_d4g0v0_Cancun + - revertRetDataSize_d5g0v0_Cancun + - revertRetDataSize_d6g0v0_Cancun + - revertRetDataSize_d7g0v0_Cancun + - revertRetDataSize_d8g0v0_Cancun + - revertRetDataSize_d9g0v0_Cancun + stRevertTest: + - LoopCallsDepthThenRevert2_d0g0v0_Cancun + - LoopCallsDepthThenRevert3_d0g0v0_Cancun + - LoopCallsThenRevert_d0g0v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d10g1v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d10g2v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d15g2v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d18g1v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d18g2v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d23g2v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d26g1v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d26g2v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d2g1v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d2g2v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d31g2v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d7g2v0_Cancun + - RevertPrecompiledTouch_Paris_d0g0v0_Cancun + - RevertPrecompiledTouch_Paris_d1g0v0_Cancun + - RevertPrecompiledTouch_Paris_d2g0v0_Cancun + - RevertPrecompiledTouch_Paris_d3g0v0_Cancun + - RevertPrecompiledTouch_nonce_d0g0v0_Cancun + - RevertPrecompiledTouch_nonce_d1g0v0_Cancun + - RevertPrecompiledTouch_nonce_d2g0v0_Cancun + - RevertPrecompiledTouch_nonce_d3g0v0_Cancun + - RevertPrecompiledTouch_noncestorage_d0g0v0_Cancun + - RevertPrecompiledTouch_noncestorage_d1g0v0_Cancun + - RevertPrecompiledTouch_noncestorage_d2g0v0_Cancun + - RevertPrecompiledTouch_noncestorage_d3g0v0_Cancun + - RevertPrecompiledTouch_storage_Paris_d0g0v0_Cancun + - RevertPrecompiledTouch_storage_Paris_d1g0v0_Cancun + - RevertPrecompiledTouch_storage_Paris_d2g0v0_Cancun + - RevertPrecompiledTouch_storage_Paris_d3g0v0_Cancun + - costRevert_d0g0v0_Cancun + - costRevert_d14g0v0_Cancun + - costRevert_d21g0v0_Cancun + - costRevert_d7g0v0_Cancun + - stateRevert_d0g0v0_Cancun + - RevertInCreateInInit_Paris_d0g0v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d15g1v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d23g1v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d31g1v0_Cancun + - RevertPrecompiledTouchExactOOG_Paris_d7g1v0_Cancun + stSStoreTest: + - SstoreCallToSelfSubRefundBelowZero_d0g0v0_Cancun + - InitCollisionParis_d0g0v0_Cancun + - InitCollisionParis_d1g0v0_Cancun + - InitCollisionParis_d2g0v0_Cancun + - InitCollisionParis_d3g0v0_Cancun + stSelfBalance: + - diffPlaces_d34g0v0_Cancun + stSolidityTest: + - CallInfiniteLoop_d0g0v0_Cancun + - TestCryptographicFunctions_d0g0v0_Cancun + stSpecialTest: + - JUMPDEST_Attack_d0g0v0_Cancun + - JUMPDEST_AttackwithJump_d0g0v0_Cancun + - block504980_d0g0v0_Cancun + - selfdestructEIP2929_d0g0v0_Cancun + - sha3_deja_d0g0v0_Cancun + - failed_tx_xcf416c53_Paris_d0g0v0_Cancun + stShift: + - shiftCombinations_d0g0v0_Cancun + - shiftSignedCombinations_d0g0v0_Cancun + stStaticCall: + - static_Call1024BalanceTooLow2_d1g0v0_Cancun + - static_Call1024BalanceTooLow_d1g0v0_Cancun + - static_Call1024PreCalls2_d0g0v0_Cancun + - static_Call1024PreCalls2_d1g0v0_Cancun + - static_Call1024PreCalls3_d1g0v0_Cancun + - static_Call1024PreCalls_d1g0v0_Cancun + - static_Call50000bytesContract50_3_d0g0v0_Cancun + - static_Call50000bytesContract50_3_d1g0v0_Cancun + - static_CallEcrecover0_0input_d3g0v0_Cancun + - static_CallEcrecover0_0input_d8g0v0_Cancun + - static_CallGoesOOGOnSecondLevel2_d1g0v0_Cancun + - static_CallRecursiveBomb0_OOG_atMaxCallDepth_d0g0v0_Cancun + - static_CallRecursiveBombPreCall2_d0g0v0_Cancun + - static_CallRecursiveBombPreCall_d0g0v0_Cancun + - static_CallRipemd160_1_d0g0v0_Cancun + - static_CallRipemd160_2_d0g0v0_Cancun + - static_CallRipemd160_3_d0g0v0_Cancun + - static_CallRipemd160_3_postfixed0_d0g0v0_Cancun + - static_CallRipemd160_3_prefixed0_d0g0v0_Cancun + - static_CallRipemd160_4_d0g0v0_Cancun + - static_CallRipemd160_4_gas719_d0g0v0_Cancun + - static_LoopCallsDepthThenRevert2_d0g0v0_Cancun + - static_LoopCallsDepthThenRevert3_d0g0v0_Cancun + - static_callChangeRevert_d1g0v0_Cancun + - static_callCreate2_d2g0v0_Cancun + - static_callcall_00_OOGE_1_d1g0v0_Cancun + - static_callcall_00_OOGE_2_d1g0v0_Cancun + - static_callcallcall_000_OOGMAfter2_d0g0v0_Cancun + - static_callcallcall_000_OOGMBefore_d0g0v0_Cancun + - static_callcallcallcode_001_OOGE_2_d1g0v0_Cancun + - static_callcallcallcode_001_OOGE_d0g0v0_Cancun + - static_callcallcallcode_001_OOGMAfter2_d1g0v0_Cancun + - static_callcallcallcode_001_OOGMAfter_3_d1g0v0_Cancun + - static_callcallcallcode_001_OOGMAfter_d1g0v0_Cancun + - static_callcallcallcode_001_OOGMBefore2_d1g0v0_Cancun + - static_callcallcallcode_001_OOGMBefore_d0g0v0_Cancun + - static_callcallcodecall_010_OOGE_2_d1g0v0_Cancun + - static_callcallcodecall_010_OOGE_d0g0v0_Cancun + - static_callcallcodecall_010_OOGMAfter_2_d1g0v0_Cancun + - static_callcallcodecall_010_OOGMAfter_3_d1g0v0_Cancun + - static_callcallcodecall_010_OOGMAfter_d1g0v0_Cancun + - static_callcallcodecall_010_OOGMBefore2_d0g0v0_Cancun + - static_callcallcodecall_010_OOGMBefore_d1g0v0_Cancun + - static_callcodecallcall_100_OOGMAfter2_d0g0v0_Cancun + - static_callcodecallcall_100_OOGMAfter2_d0g0v1_Cancun + - static_callcodecallcall_100_OOGMAfter_2_d0g0v0_Cancun + - static_callcodecallcallcode_101_OOGE_d0g0v0_Cancun + - static_callcodecallcallcode_101_OOGMAfter_1_d0g0v0_Cancun + - StaticcallToPrecompileFromCalledContract_d0g0v0_Cancun + - StaticcallToPrecompileFromContractInitialization_d0g0v0_Cancun + - StaticcallToPrecompileFromTransaction_d0g0v0_Cancun + - static_Call1MB1024Calldepth_d1g0v0_Cancun + - static_Call50000_d0g0v0_Cancun + - static_Call50000_d1g0v0_Cancun + - static_Call50000_ecrec_d0g0v0_Cancun + - static_Call50000_ecrec_d1g0v0_Cancun + - static_Call50000_identity2_d0g0v0_Cancun + - static_Call50000_identity2_d1g0v0_Cancun + - static_Call50000_identity_d0g0v0_Cancun + - static_Call50000_identity_d1g0v0_Cancun + - static_Call50000_rip160_d0g0v0_Cancun + - static_Call50000_rip160_d1g0v0_Cancun + - static_Call50000bytesContract50_1_d0g0v0_Cancun + - static_Call50000bytesContract50_1_d1g0v0_Cancun + - static_Call50000bytesContract50_2_d0g0v0_Cancun + - static_Call50000bytesContract50_2_d1g0v0_Cancun + - static_CallRipemd160_5_d0g0v0_Cancun + - static_LoopCallsThenRevert_d0g0v0_Cancun + - static_LoopCallsThenRevert_d0g1v0_Cancun + - static_Return50000_2_d0g0v0_Cancun + stSystemOperationsTest: + - CallRecursiveBomb0_OOG_atMaxCallDepth_d0g0v0_Cancun + stZeroKnowledge: + - ecpairing_bad_length_191_d0g0v0_Cancun + - ecpairing_bad_length_191_d0g1v0_Cancun + - ecpairing_bad_length_191_d0g2v0_Cancun + - ecpairing_bad_length_191_d0g3v0_Cancun + - ecpairing_bad_length_193_d0g0v0_Cancun + - ecpairing_bad_length_193_d0g1v0_Cancun + - ecpairing_bad_length_193_d0g2v0_Cancun + - ecpairing_bad_length_193_d0g3v0_Cancun + - ecpairing_empty_data_d0g1v0_Cancun + - ecpairing_empty_data_d0g2v0_Cancun + - ecpairing_empty_data_insufficient_gas_d0g0v0_Cancun + - ecpairing_empty_data_insufficient_gas_d0g1v0_Cancun + - ecpairing_inputs_d100g0v0_Cancun + - ecpairing_inputs_d101g0v0_Cancun + - ecpairing_inputs_d102g0v0_Cancun + - ecpairing_inputs_d103g0v0_Cancun + - ecpairing_inputs_d104g0v0_Cancun + - ecpairing_inputs_d105g0v0_Cancun + - ecpairing_inputs_d106g0v0_Cancun + - ecpairing_inputs_d107g0v0_Cancun + - ecpairing_inputs_d108g0v0_Cancun + - ecpairing_inputs_d109g0v0_Cancun + - ecpairing_inputs_d110g0v0_Cancun + - ecpairing_inputs_d111g0v0_Cancun + - ecpairing_inputs_d112g0v0_Cancun + - ecpairing_inputs_d113g0v0_Cancun + - ecpairing_inputs_d114g0v0_Cancun + - ecpairing_inputs_d115g0v0_Cancun + - ecpairing_inputs_d116g0v0_Cancun + - ecpairing_inputs_d117g0v0_Cancun + - ecpairing_inputs_d118g0v0_Cancun + - ecpairing_inputs_d119g0v0_Cancun + - ecpairing_inputs_d120g0v0_Cancun + - ecpairing_inputs_d121g0v0_Cancun + - ecpairing_inputs_d122g0v0_Cancun + - ecpairing_inputs_d123g0v0_Cancun + - ecpairing_inputs_d124g0v0_Cancun + - ecpairing_inputs_d125g0v0_Cancun + - ecpairing_inputs_d126g0v0_Cancun + - ecpairing_inputs_d127g0v0_Cancun + - ecpairing_inputs_d128g0v0_Cancun + - ecpairing_inputs_d129g0v0_Cancun + - ecpairing_inputs_d130g0v0_Cancun + - ecpairing_inputs_d131g0v0_Cancun + - ecpairing_inputs_d132g0v0_Cancun + - ecpairing_inputs_d133g0v0_Cancun + - ecpairing_inputs_d134g0v0_Cancun + - ecpairing_inputs_d135g0v0_Cancun + - ecpairing_inputs_d136g0v0_Cancun + - ecpairing_inputs_d137g0v0_Cancun + - ecpairing_inputs_d138g0v0_Cancun + - ecpairing_inputs_d139g0v0_Cancun + - ecpairing_inputs_d140g0v0_Cancun + - ecpairing_inputs_d141g0v0_Cancun + - ecpairing_inputs_d142g0v0_Cancun + - ecpairing_inputs_d143g0v0_Cancun + - ecpairing_inputs_d38g0v0_Cancun + - ecpairing_inputs_d39g0v0_Cancun + - ecpairing_inputs_d40g0v0_Cancun + - ecpairing_inputs_d41g0v0_Cancun + - ecpairing_inputs_d42g0v0_Cancun + - ecpairing_inputs_d43g0v0_Cancun + - ecpairing_inputs_d44g0v0_Cancun + - ecpairing_inputs_d45g0v0_Cancun + - ecpairing_inputs_d46g0v0_Cancun + - ecpairing_inputs_d47g0v0_Cancun + - ecpairing_inputs_d48g0v0_Cancun + - ecpairing_inputs_d49g0v0_Cancun + - ecpairing_inputs_d50g0v0_Cancun + - ecpairing_inputs_d51g0v0_Cancun + - ecpairing_inputs_d52g0v0_Cancun + - ecpairing_inputs_d53g0v0_Cancun + - ecpairing_inputs_d54g0v0_Cancun + - ecpairing_inputs_d55g0v0_Cancun + - ecpairing_inputs_d56g0v0_Cancun + - ecpairing_inputs_d57g0v0_Cancun + - ecpairing_inputs_d58g0v0_Cancun + - ecpairing_inputs_d59g0v0_Cancun + - ecpairing_inputs_d60g0v0_Cancun + - ecpairing_inputs_d61g0v0_Cancun + - ecpairing_inputs_d62g0v0_Cancun + - ecpairing_inputs_d63g0v0_Cancun + - ecpairing_inputs_d64g0v0_Cancun + - ecpairing_inputs_d65g0v0_Cancun + - ecpairing_inputs_d66g0v0_Cancun + - ecpairing_inputs_d67g0v0_Cancun + - ecpairing_inputs_d68g0v0_Cancun + - ecpairing_inputs_d69g0v0_Cancun + - ecpairing_inputs_d70g0v0_Cancun + - ecpairing_inputs_d71g0v0_Cancun + - ecpairing_inputs_d72g0v0_Cancun + - ecpairing_inputs_d73g0v0_Cancun + - ecpairing_inputs_d74g0v0_Cancun + - ecpairing_inputs_d75g0v0_Cancun + - ecpairing_inputs_d76g0v0_Cancun + - ecpairing_inputs_d77g0v0_Cancun + - ecpairing_inputs_d78g0v0_Cancun + - ecpairing_inputs_d79g0v0_Cancun + - ecpairing_inputs_d80g0v0_Cancun + - ecpairing_inputs_d81g0v0_Cancun + - ecpairing_inputs_d82g0v0_Cancun + - ecpairing_inputs_d83g0v0_Cancun + - ecpairing_inputs_d84g0v0_Cancun + - ecpairing_inputs_d85g0v0_Cancun + - ecpairing_inputs_d86g0v0_Cancun + - ecpairing_inputs_d87g0v0_Cancun + - ecpairing_inputs_d88g0v0_Cancun + - ecpairing_inputs_d89g0v0_Cancun + - ecpairing_inputs_d90g0v0_Cancun + - ecpairing_inputs_d91g0v0_Cancun + - ecpairing_inputs_d92g0v0_Cancun + - ecpairing_inputs_d93g0v0_Cancun + - ecpairing_inputs_d94g0v0_Cancun + - ecpairing_inputs_d95g0v0_Cancun + - ecpairing_inputs_d96g0v0_Cancun + - ecpairing_inputs_d97g0v0_Cancun + - ecpairing_inputs_d98g0v0_Cancun + - ecpairing_inputs_d99g0v0_Cancun + - ecpairing_one_point_fail_d0g1v0_Cancun + - ecpairing_one_point_fail_d0g2v0_Cancun + - ecpairing_one_point_insufficient_gas_d0g0v0_Cancun + - ecpairing_one_point_insufficient_gas_d0g1v0_Cancun + - ecpairing_one_point_insufficient_gas_d0g2v0_Cancun + - ecpairing_one_point_not_in_subgroup_d0g0v0_Cancun + - ecpairing_one_point_not_in_subgroup_d0g1v0_Cancun + - ecpairing_one_point_not_in_subgroup_d0g2v0_Cancun + - ecpairing_one_point_not_in_subgroup_d0g3v0_Cancun + - ecpairing_one_point_with_g1_zero_d0g1v0_Cancun + - ecpairing_one_point_with_g1_zero_d0g2v0_Cancun + - ecpairing_one_point_with_g2_zero_and_g1_invalid_d0g0v0_Cancun + - ecpairing_one_point_with_g2_zero_and_g1_invalid_d0g1v0_Cancun + - ecpairing_one_point_with_g2_zero_and_g1_invalid_d0g2v0_Cancun + - ecpairing_one_point_with_g2_zero_and_g1_invalid_d0g3v0_Cancun + - ecpairing_one_point_with_g2_zero_d0g1v0_Cancun + - ecpairing_perturb_g2_by_curve_order_d0g0v0_Cancun + - ecpairing_perturb_g2_by_curve_order_d0g1v0_Cancun + - ecpairing_perturb_g2_by_curve_order_d0g2v0_Cancun + - ecpairing_perturb_g2_by_curve_order_d0g3v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_again_d0g0v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_again_d0g1v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_again_d0g2v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_again_d0g3v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_d0g0v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_d0g1v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_d0g2v0_Cancun + - ecpairing_perturb_g2_by_field_modulus_d0g3v0_Cancun + - ecpairing_perturb_g2_by_one_d0g0v0_Cancun + - ecpairing_perturb_g2_by_one_d0g1v0_Cancun + - ecpairing_perturb_g2_by_one_d0g2v0_Cancun + - ecpairing_perturb_g2_by_one_d0g3v0_Cancun + - ecpairing_perturb_zeropoint_by_curve_order_d0g0v0_Cancun + - ecpairing_perturb_zeropoint_by_curve_order_d0g1v0_Cancun + - ecpairing_perturb_zeropoint_by_curve_order_d0g2v0_Cancun + - ecpairing_perturb_zeropoint_by_curve_order_d0g3v0_Cancun + - ecpairing_perturb_zeropoint_by_field_modulus_d0g0v0_Cancun + - ecpairing_perturb_zeropoint_by_field_modulus_d0g1v0_Cancun + - ecpairing_perturb_zeropoint_by_field_modulus_d0g2v0_Cancun + - ecpairing_perturb_zeropoint_by_field_modulus_d0g3v0_Cancun + - ecpairing_perturb_zeropoint_by_one_d0g0v0_Cancun + - ecpairing_perturb_zeropoint_by_one_d0g1v0_Cancun + - ecpairing_perturb_zeropoint_by_one_d0g2v0_Cancun + - ecpairing_perturb_zeropoint_by_one_d0g3v0_Cancun + - ecpairing_three_point_fail_1_d0g1v0_Cancun + - ecpairing_three_point_fail_1_d0g2v0_Cancun + - ecpairing_three_point_fail_1_d0g3v0_Cancun + - ecpairing_three_point_match_1_d0g1v0_Cancun + - ecpairing_three_point_match_1_d0g2v0_Cancun + - ecpairing_two_point_fail_1_d0g1v0_Cancun + - ecpairing_two_point_fail_1_d0g2v0_Cancun + - ecpairing_two_point_fail_2_d0g1v0_Cancun + - ecpairing_two_point_fail_2_d0g2v0_Cancun + - ecpairing_two_point_fail_2_d0g3v0_Cancun + - ecpairing_two_point_match_1_d0g1v0_Cancun + - ecpairing_two_point_match_1_d0g2v0_Cancun + - ecpairing_two_point_match_1_d0g3v0_Cancun + - ecpairing_two_point_match_2_d0g1v0_Cancun + - ecpairing_two_point_match_2_d0g2v0_Cancun + - ecpairing_two_point_match_3_d0g1v0_Cancun + - ecpairing_two_point_match_3_d0g2v0_Cancun + - ecpairing_two_point_match_4_d0g1v0_Cancun + - ecpairing_two_point_match_4_d0g2v0_Cancun + - ecpairing_two_point_match_5_d0g1v0_Cancun + - ecpairing_two_point_match_5_d0g2v0_Cancun + - ecpairing_two_point_match_5_d0g3v0_Cancun + - ecpairing_two_point_oog_d0g1v0_Cancun + - ecpairing_two_point_oog_d0g2v0_Cancun + - ecpairing_two_points_with_one_g2_zero_d0g1v0_Cancun + - ecpairing_two_points_with_one_g2_zero_d0g2v0_Cancun + - pairingTest_d0g1v0_Cancun + - pairingTest_d0g2v0_Cancun + - pairingTest_d0g3v0_Cancun + - pairingTest_d1g1v0_Cancun + - pairingTest_d1g2v0_Cancun + - pairingTest_d1g3v0_Cancun + - pairingTest_d2g1v0_Cancun + - pairingTest_d2g2v0_Cancun + - pairingTest_d2g3v0_Cancun + - pairingTest_d3g1v0_Cancun + - pairingTest_d3g2v0_Cancun + - pairingTest_d3g3v0_Cancun + - pairingTest_d4g1v0_Cancun + - pairingTest_d4g2v0_Cancun + - pairingTest_d4g3v0_Cancun + - pairingTest_d5g1v0_Cancun + - pairingTest_d5g2v0_Cancun + - pairingTest_d5g3v0_Cancun + - ecpairing_empty_data_d0g0v0_Cancun + - ecpairing_empty_data_d0g3v0_Cancun + - ecpairing_empty_data_insufficient_gas_d0g2v0_Cancun + - ecpairing_inputs_d0g0v0_Cancun + - ecpairing_inputs_d10g0v0_Cancun + - ecpairing_inputs_d11g0v0_Cancun + - ecpairing_inputs_d12g0v0_Cancun + - ecpairing_inputs_d13g0v0_Cancun + - ecpairing_inputs_d14g0v0_Cancun + - ecpairing_inputs_d15g0v0_Cancun + - ecpairing_inputs_d16g0v0_Cancun + - ecpairing_inputs_d17g0v0_Cancun + - ecpairing_inputs_d18g0v0_Cancun + - ecpairing_inputs_d19g0v0_Cancun + - ecpairing_inputs_d1g0v0_Cancun + - ecpairing_inputs_d20g0v0_Cancun + - ecpairing_inputs_d21g0v0_Cancun + - ecpairing_inputs_d22g0v0_Cancun + - ecpairing_inputs_d23g0v0_Cancun + - ecpairing_inputs_d24g0v0_Cancun + - ecpairing_inputs_d25g0v0_Cancun + - ecpairing_inputs_d26g0v0_Cancun + - ecpairing_inputs_d27g0v0_Cancun + - ecpairing_inputs_d28g0v0_Cancun + - ecpairing_inputs_d29g0v0_Cancun + - ecpairing_inputs_d2g0v0_Cancun + - ecpairing_inputs_d30g0v0_Cancun + - ecpairing_inputs_d31g0v0_Cancun + - ecpairing_inputs_d32g0v0_Cancun + - ecpairing_inputs_d33g0v0_Cancun + - ecpairing_inputs_d34g0v0_Cancun + - ecpairing_inputs_d35g0v0_Cancun + - ecpairing_inputs_d36g0v0_Cancun + - ecpairing_inputs_d37g0v0_Cancun + - ecpairing_inputs_d3g0v0_Cancun + - ecpairing_inputs_d4g0v0_Cancun + - ecpairing_inputs_d5g0v0_Cancun + - ecpairing_inputs_d6g0v0_Cancun + - ecpairing_inputs_d7g0v0_Cancun + - ecpairing_inputs_d8g0v0_Cancun + - ecpairing_inputs_d9g0v0_Cancun + - ecpairing_one_point_fail_d0g0v0_Cancun + - ecpairing_one_point_with_g1_zero_d0g0v0_Cancun + - ecpairing_one_point_with_g2_zero_d0g0v0_Cancun + - ecpairing_one_point_with_g2_zero_d0g2v0_Cancun + - ecpairing_one_point_with_g2_zero_d0g3v0_Cancun + - ecpairing_three_point_fail_1_d0g0v0_Cancun + - ecpairing_three_point_match_1_d0g0v0_Cancun + - ecpairing_three_point_match_1_d0g3v0_Cancun + - ecpairing_two_point_fail_1_d0g0v0_Cancun + - ecpairing_two_point_fail_2_d0g0v0_Cancun + - ecpairing_two_point_match_1_d0g0v0_Cancun + - ecpairing_two_point_match_2_d0g0v0_Cancun + - ecpairing_two_point_match_2_d0g3v0_Cancun + - ecpairing_two_point_match_3_d0g0v0_Cancun + - ecpairing_two_point_match_3_d0g3v0_Cancun + - ecpairing_two_point_match_4_d0g0v0_Cancun + - ecpairing_two_point_match_4_d0g3v0_Cancun + - ecpairing_two_point_match_5_d0g0v0_Cancun + - ecpairing_two_point_oog_d0g0v0_Cancun + - ecpairing_two_point_oog_d0g3v0_Cancun + - ecpairing_two_points_with_one_g2_zero_d0g0v0_Cancun + - ecpairing_two_points_with_one_g2_zero_d0g3v0_Cancun + - pairingTest_d0g0v0_Cancun + - pairingTest_d1g0v0_Cancun + - pairingTest_d2g0v0_Cancun + - pairingTest_d3g0v0_Cancun + - pairingTest_d4g0v0_Cancun + - pairingTest_d5g0v0_Cancun + tstorage: + - run_until_out_of_gas__fork_Cancun_minus_blockchain_test_minus_tstore + - run_until_out_of_gas__fork_Cancun_minus_blockchain_test_minus_tstore_tload + - run_until_out_of_gas__fork_Cancun_minus_blockchain_test_minus_tstore_wide_address_space + stAttackTest: + - ContractCreationSpam_d0g0v0_Cancun + stStaticFlagEnabled: + - CallWithZeroValueToPrecompileFromCalledContract_d0g0v0_Cancun + - CallWithZeroValueToPrecompileFromContractInitialization_d0g0v0_Cancun + - CallWithZeroValueToPrecompileFromTransaction_d0g0v0_Cancun + - CallcodeToPrecompileFromCalledContract_d0g0v0_Cancun + - CallcodeToPrecompileFromContractInitialization_d0g0v0_Cancun + - CallcodeToPrecompileFromTransaction_d0g0v0_Cancun + - DelegatecallToPrecompileFromCalledContract_d0g0v0_Cancun + - DelegatecallToPrecompileFromContractInitialization_d0g0v0_Cancun + - DelegatecallToPrecompileFromTransaction_d0g0v0_Cancun + stTimeConsuming: + - CALLBlake2f_MaxRounds_d0g0v0_Cancun + - static_Call50000_sha256_d0g0v0_Cancun + - static_Call50000_sha256_d1g0v0_Cancun + stTransactionTest: + - HighGasPriceParis_d0g0v0_Cancun + - ValueOverflowParis_d0g0v0_Cancun diff --git a/cairo/kakarot-ssj/bun.lockb b/cairo/kakarot-ssj/bun.lockb new file mode 100755 index 000000000..ad0e90537 Binary files /dev/null and b/cairo/kakarot-ssj/bun.lockb differ diff --git a/cairo1_contracts/mock_pragma/.gitignore b/cairo/kakarot-ssj/crates/alexandria_data_structures/.gitignore similarity index 100% rename from cairo1_contracts/mock_pragma/.gitignore rename to cairo/kakarot-ssj/crates/alexandria_data_structures/.gitignore diff --git a/cairo/kakarot-ssj/crates/alexandria_data_structures/Scarb.toml b/cairo/kakarot-ssj/crates/alexandria_data_structures/Scarb.toml new file mode 100644 index 000000000..d3fd53bcd --- /dev/null +++ b/cairo/kakarot-ssj/crates/alexandria_data_structures/Scarb.toml @@ -0,0 +1,15 @@ +[package] +name = "alexandria_data_structures" +version = "0.1.0" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } +assert_macros = "2.8.2" + +[scripts] +test = "snforge test --max-n-steps 4294967295" +test-profiling = "snforge test --max-n-steps 4294967295 --build-profile" diff --git a/cairo/kakarot-ssj/crates/alexandria_data_structures/src/lib.cairo b/cairo/kakarot-ssj/crates/alexandria_data_structures/src/lib.cairo new file mode 100644 index 000000000..63e55caa6 --- /dev/null +++ b/cairo/kakarot-ssj/crates/alexandria_data_structures/src/lib.cairo @@ -0,0 +1 @@ +pub mod vec; diff --git a/cairo/kakarot-ssj/crates/alexandria_data_structures/src/vec.cairo b/cairo/kakarot-ssj/crates/alexandria_data_structures/src/vec.cairo new file mode 100644 index 000000000..237adaff2 --- /dev/null +++ b/cairo/kakarot-ssj/crates/alexandria_data_structures/src/vec.cairo @@ -0,0 +1,167 @@ +use core::nullable::NullableImpl; +use core::num::traits::WrappingAdd; +use core::ops::index::Index; + +//! Vec implementation. +//! +//! # Example +//! ``` +//! use alexandria::data_structures::vec::VecTrait; +//! +//! // Create a new vec instance. +//! let mut vec = Felt252Vec::::new(); +//! // Push some items to the vec. +//! vec.push(1); +//! vec.push(2); +//! ... +//! ``` + +pub trait VecTrait { + /// Creates a new V instance. + /// Returns + /// * V The new vec instance. + fn new() -> V; + + /// Returns the item at the given index, or None if the index is out of bounds. + /// Parameters + /// * self The vec instance. + /// * index The index of the item to get. + /// Returns + /// * Option The item at the given index, or None if the index is out of bounds. + fn get(ref self: V, index: usize) -> Option; + + /// Returns the item at the given index, or panics if the index is out of bounds. + /// Parameters + /// * self The vec instance. + /// * index The index of the item to get. + /// Returns + /// * T The item at the given index. + fn at(ref self: V, index: usize) -> T; + + /// Pushes a new item to the vec. + /// Parameters + /// * self The vec instance. + /// * value The value to push onto the vec. + fn push(ref self: V, value: T); + + /// Sets the item at the given index to the given value. + /// Panics if the index is out of bounds. + /// Parameters + /// * self The vec instance. + /// * index The index of the item to set. + /// * value The value to set the item to. + fn set(ref self: V, index: usize, value: T); + + /// Returns the length of the vec. + /// Parameters + /// * self The vec instance. + /// Returns + /// * usize The length of the vec. + fn len(self: @V) -> usize; +} + +impl VecIndex> of Index { + type Target = T; + + #[inline(always)] + fn index(ref self: V, index: usize) -> T { + self.at(index) + } +} + +pub struct Felt252Vec { + pub items: Felt252Dict, + pub len: usize, +} + +impl DefaultFeltVec, +Copy, +Felt252DictValue> of Default> { + fn default() -> Felt252Vec { + Felt252VecImpl::::new() + } +} + +impl DestructFeltVec, +Felt252DictValue> of Destruct> { + fn destruct(self: Felt252Vec) nopanic { + self.items.squash(); + } +} + + +impl Felt252VecImpl, +Copy, +Felt252DictValue> of VecTrait, T> { + fn new() -> Felt252Vec { + Felt252Vec { items: Default::default(), len: 0 } + } + + fn get(ref self: Felt252Vec, index: usize) -> Option { + if index < self.len() { + let item = self.items.get(index.into()); + Option::Some(item) + } else { + Option::None + } + } + + fn at(ref self: Felt252Vec, index: usize) -> T { + assert(index < self.len(), 'Index out of bounds'); + let item = self.items.get(index.into()); + item + } + + fn push(ref self: Felt252Vec, value: T) { + self.items.insert(self.len.into(), value); + self.len = self.len.wrapping_add(1); + } + + fn set(ref self: Felt252Vec, index: usize, value: T) { + assert(index < self.len(), 'Index out of bounds'); + self.items.insert(index.into(), value); + } + + fn len(self: @Felt252Vec) -> usize { + *self.len + } +} + +pub struct NullableVec { + items: Felt252Dict>, + len: usize, +} + +impl DestructNullableVec> of Destruct> { + fn destruct(self: NullableVec) nopanic { + self.items.squash(); + } +} + +impl NullableVecImpl, +Copy> of VecTrait, T> { + fn new() -> NullableVec { + NullableVec { items: Default::default(), len: 0 } + } + + fn get(ref self: NullableVec, index: usize) -> Option { + if index < self.len() { + Option::Some(self.items.get(index.into()).deref()) + } else { + Option::None + } + } + + fn at(ref self: NullableVec, index: usize) -> T { + assert(index < self.len(), 'Index out of bounds'); + self.items.get(index.into()).deref() + } + + fn push(ref self: NullableVec, value: T) { + self.items.insert(self.len.into(), NullableImpl::new(value)); + self.len = self.len.wrapping_add(1); + } + + fn set(ref self: NullableVec, index: usize, value: T) { + assert(index < self.len(), 'Index out of bounds'); + self.items.insert(index.into(), NullableImpl::new(value)); + } + + fn len(self: @NullableVec) -> usize { + *self.len + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/Scarb.toml b/cairo/kakarot-ssj/crates/contracts/Scarb.toml new file mode 100644 index 000000000..1e689c03e --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/Scarb.toml @@ -0,0 +1,32 @@ +[package] +name = "contracts" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet.workspace = true +evm = { path = "../evm" } +openzeppelin = { path = "../openzeppelin" } +utils = { path = "../utils" } + +[tool] +fmt.workspace = true + +[[target.starknet-contract]] +casm = true +casm-add-pythonic-hints = true +build-external-contracts = ["openzeppelin::token::erc20::erc20::ERC20"] + +[lib] +name = "contracts" + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } +assert_macros = "2.8.2" +snforge_utils = { path = "../snforge_utils" } + +[scripts] +test = "snforge test --max-n-steps 4294967295" +test-profiling = "snforge test --max-n-steps 4294967295 --build-profile" diff --git a/cairo/kakarot-ssj/crates/contracts/src/account_contract.cairo b/cairo/kakarot-ssj/crates/contracts/src/account_contract.cairo new file mode 100644 index 000000000..5de99dd12 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/account_contract.cairo @@ -0,0 +1,278 @@ +//! The generic account that is deployed by Kakarot Core before being "specialized" into an +//! Externally Owned Account or a Contract Account This aims at having only one class hash for all +//! the contracts deployed by Kakarot, thus enforcing a unique and consistent address mapping Eth +//! Address <=> Starknet Address + +use core::starknet::account::{Call}; +use core::starknet::{EthAddress, ClassHash, ContractAddress}; + +#[derive(Copy, Drop, Serde, Debug)] +pub struct OutsideExecution { + pub caller: ContractAddress, + pub nonce: u64, + pub execute_after: u64, + pub execute_before: u64, + pub calls: Span +} + +#[starknet::interface] +pub trait IAccount { + fn initialize( + ref self: TContractState, evm_address: EthAddress, implementation_class: ClassHash + ); + fn get_implementation(self: @TContractState) -> ClassHash; + fn get_evm_address(self: @TContractState) -> EthAddress; + fn get_code_hash(self: @TContractState) -> u256; + fn set_code_hash(ref self: TContractState, code_hash: u256); + fn is_initialized(self: @TContractState) -> bool; + + // EOA functions + fn __validate__(ref self: TContractState, calls: Array) -> felt252; + fn __validate_declare__(self: @TContractState, class_hash: felt252) -> felt252; + fn __execute__(ref self: TContractState, calls: Array) -> Array>; + + // CA functions + fn write_bytecode(ref self: TContractState, bytecode: Span); + fn bytecode(self: @TContractState) -> Span; + fn write_storage(ref self: TContractState, key: u256, value: u256); + fn storage(self: @TContractState, key: u256) -> u256; + fn get_nonce(self: @TContractState) -> u64; + fn set_nonce(ref self: TContractState, nonce: u64); + fn execute_starknet_call(ref self: TContractState, call: Call) -> (bool, Span); + fn execute_from_outside( + ref self: TContractState, outside_execution: OutsideExecution, signature: Span, + ) -> Array>; +} + +#[starknet::contract(account)] +pub mod AccountContract { + use core::cmp::min; + use core::num::traits::Bounded; + use core::num::traits::zero::Zero; + use core::starknet::account::{Call}; + use core::starknet::eth_signature::verify_eth_signature; + use core::starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; + use core::starknet::syscalls::call_contract_syscall; + use core::starknet::{ + EthAddress, ClassHash, get_caller_address, get_tx_info, get_block_timestamp + }; + use crate::components::ownable::IOwnable; + use crate::components::ownable::ownable_component::InternalTrait; + use crate::components::ownable::ownable_component; + use crate::errors::KAKAROT_REENTRANCY; + use crate::kakarot_core::eth_rpc::{IEthRPCDispatcher, IEthRPCDispatcherTrait}; + use crate::kakarot_core::interface::{IKakarotCoreDispatcher, IKakarotCoreDispatcherTrait}; + use crate::storage::StorageBytecode; + use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; + use super::OutsideExecution; + use utils::eth_transaction::transaction::TransactionTrait; + use utils::serialization::{deserialize_signature, deserialize_bytes, serialize_bytes}; + use utils::traits::DefaultSignature; + + // Add ownable component + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::Ownable; + impl OwnableInternal = ownable_component::InternalImpl; + + + const VERSION: u32 = 000_001_000; + + + #[storage] + pub(crate) struct Storage { + pub(crate) Account_bytecode: StorageBytecode, + pub(crate) Account_bytecode_len: u32, + pub(crate) Account_storage: Map, + pub(crate) Account_is_initialized: bool, + pub(crate) Account_nonce: u64, + pub(crate) Account_implementation: ClassHash, + pub(crate) Account_evm_address: EthAddress, + pub(crate) Account_code_hash: u256, + #[substorage(v0)] + ownable: ownable_component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + transaction_executed: TransactionExecuted, + OwnableEvent: ownable_component::Event + } + + #[derive(Drop, starknet::Event, Debug)] + pub struct TransactionExecuted { + pub response: Span, + pub success: bool, + pub gas_used: u64 + } + + #[constructor] + fn constructor(ref self: ContractState) { + panic!("Accounts cannot be created directly"); + } + + #[abi(embed_v0)] + impl Account of super::IAccount { + fn initialize( + ref self: ContractState, evm_address: EthAddress, implementation_class: ClassHash + ) { + assert(!self.Account_is_initialized.read(), 'Account already initialized'); + self.Account_is_initialized.write(true); + + self.Account_evm_address.write(evm_address); + self.Account_implementation.write(implementation_class); + + let kakarot_address = self.ownable.owner(); + let kakarot = IKakarotCoreDispatcher { contract_address: kakarot_address }; + let native_token = kakarot.get_native_token(); + // To internally perform value transfer of the network's native + // token (which conforms to the ERC20 standard), we need to give the + // KakarotCore contract infinite allowance + IERC20CamelDispatcher { contract_address: native_token } + .approve(kakarot_address, Bounded::::MAX); + + kakarot.register_account(evm_address); + } + + fn get_implementation(self: @ContractState) -> ClassHash { + self.Account_implementation.read() + } + + fn get_evm_address(self: @ContractState) -> EthAddress { + self.Account_evm_address.read() + } + + fn get_code_hash(self: @ContractState) -> u256 { + self.Account_code_hash.read() + } + + fn set_code_hash(ref self: ContractState, code_hash: u256) { + self.ownable.assert_only_owner(); + self.Account_code_hash.write(code_hash); + } + + fn is_initialized(self: @ContractState) -> bool { + self.Account_is_initialized.read() + } + + // EOA functions + fn __validate__(ref self: ContractState, calls: Array) -> felt252 { + panic!("EOA: __validate__ not supported") + } + + /// Validate Declare is not used for Kakarot + fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 { + panic!("EOA: declare not supported") + } + + fn __execute__(ref self: ContractState, calls: Array) -> Array> { + panic!("EOA: __execute__ not supported") + } + + fn write_bytecode(ref self: ContractState, bytecode: Span) { + self.ownable.assert_only_owner(); + self.Account_bytecode.write(StorageBytecode { bytecode }); + } + + fn bytecode(self: @ContractState) -> Span { + self.Account_bytecode.read().bytecode + } + + fn write_storage(ref self: ContractState, key: u256, value: u256) { + self.ownable.assert_only_owner(); + self.Account_storage.write(key, value); + } + + fn storage(self: @ContractState, key: u256) -> u256 { + self.Account_storage.read(key) + } + + fn get_nonce(self: @ContractState) -> u64 { + self.Account_nonce.read() + } + + fn set_nonce(ref self: ContractState, nonce: u64) { + self.ownable.assert_only_owner(); + self.Account_nonce.write(nonce); + } + + /// Used to preserve caller in Cairo Precompiles + /// Reentrency check is done for Kakarot contract, only get_starknet_address is allowed + /// for Solidity contracts to be able to get the corresponding Starknet address in their + /// calldata. + fn execute_starknet_call(ref self: ContractState, call: Call) -> (bool, Span) { + self.ownable.assert_only_owner(); + let kakarot_address = self.ownable.owner(); + if call.to == kakarot_address && call.selector != selector!("get_starknet_address") { + return (false, KAKAROT_REENTRANCY.span()); + } + let response = call_contract_syscall(call.to, call.selector, call.calldata); + if response.is_ok() { + return (true, response.unwrap().into()); + } + return (false, response.unwrap_err().into()); + } + + fn execute_from_outside( + ref self: ContractState, outside_execution: OutsideExecution, signature: Span, + ) -> Array> { + let caller = get_caller_address(); + let tx_info = get_tx_info(); + + // SNIP-9 Validation + if (outside_execution.caller.into() != 'ANY_CALLER') { + assert(caller == outside_execution.caller, 'SNIP9: Invalid caller'); + } + + let block_timestamp = get_block_timestamp(); + assert(block_timestamp > outside_execution.execute_after, 'SNIP9: Too early call'); + assert(block_timestamp < outside_execution.execute_before, 'SNIP9: Too late call'); + + // Kakarot-Specific Validation + assert(outside_execution.calls.len() == 1, 'KKRT: Multicall not supported'); + assert(tx_info.version.into() >= 1_u256, 'KKRT: Deprecated tx version: 0'); + + // EOA Validation + assert(self.Account_bytecode_len.read().is_zero(), 'EOA: cannot have code'); + + let kakarot = IEthRPCDispatcher { contract_address: self.ownable.owner() }; + + let chain_id: u64 = kakarot.eth_chain_id(); + assert(signature.len() == 5, 'EOA: Invalid signature length'); + let signature = deserialize_signature(signature, chain_id) + .expect('EOA: invalid signature'); + + let mut encoded_tx_data = deserialize_bytes((*outside_execution.calls[0]).calldata) + .expect('conversion to Span failed') + .span(); + let unsigned_transaction_hash = TransactionTrait::compute_hash(encoded_tx_data); + + let address = self.Account_evm_address.read(); + verify_eth_signature(unsigned_transaction_hash, signature, address); + + let (success, return_data, gas_used) = kakarot + .eth_send_raw_unsigned_tx(encoded_tx_data); + let return_data = serialize_bytes(return_data).span(); + + // See Argent account + // https://github.com/argentlabs/argent-contracts-starknet/blob/1352198956f36fb35fa544c4e46a3507a3ec20e3/src/presets/user_account.cairo#L211-L213 + // See 300 max data_len for events + // https://github.com/starkware-libs/blockifier/blob/9bfb3d4c8bf1b68a0c744d1249b32747c75a4d87/crates/blockifier/resources/versioned_constants.json + // The whole data_len should be less than 300, so it's the return_data should be less + // than 297 (+3 for return_data_len, success, gas_used) + self + .emit( + TransactionExecuted { + response: return_data.slice(0, min(297, return_data.len())), + success: success, + gas_used + } + ); + array![return_data] + } + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/cairo1_helpers.cairo b/cairo/kakarot-ssj/crates/contracts/src/cairo1_helpers.cairo new file mode 100644 index 000000000..1a22b9721 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/cairo1_helpers.cairo @@ -0,0 +1,233 @@ +use core::starknet::{EthAddress, secp256_trait::Signature}; + +#[starknet::interface] +pub trait IPrecompiles { + /// Executes a precompiled contract at a given address with provided data. + /// + /// # Arguments + /// + /// * `self` - The instance of the current class. + /// * `address` - The address of the precompiled contract to be executed. + /// * `data` - The data to be passed to the precompiled contract. + /// + /// # Returns + /// + /// * A tuple containing: + /// * True if the execution was successful, false otherwise. + /// * The gas cost of the execution if successful, otherwise 0. + /// * The output data from the execution. + fn exec_precompile(self: @T, address: felt252, data: Span) -> (bool, u64, Span); +} + +#[starknet::interface] +pub trait IHelpers { + /// Gets the hash of a specific StarkNet block within the range of + /// [first_v0_12_0_block, current_block - 10]. + /// + /// # Arguments + /// + /// * `block_number` - The block number for which to get the hash. + /// + /// # Returns + /// The hash of the specified block. + /// + /// # Errors + /// `Block number out of range` - If the block number is greater than `current_block - 10`. + /// `0`: The block number is inferior to `first_v0_12_0_block`. + fn get_block_hash(self: @T, block_number: u64) -> felt252; + + /// Computes the keccak hash of the provided data. + /// + /// The data is expected to be an array of full 64-bit words. + /// The last u64-word to hash may be incomplete and is provided separately. + /// # Arguments + /// + /// * `words` - The full 64-bit words to hash. + /// * `last_input_word` - The last word to hash. + /// * `last_input_num_bytes` - The number of bytes in the last word. + /// + /// # Returns + /// The EVM-compatible keccak hash of the provided data. + fn keccak( + self: @T, words: Array, last_input_word: u64, last_input_num_bytes: usize + ) -> u256; + + /// Computes the SHA-256 of the provided data. + /// + /// The data is expected to be an array of full 32-bit unsigned words. + /// The last u32-word to hash may be incomplete and is provided separately. + /// # Arguments + /// + /// * `input` - The full 32-bit unsigned words to hash. + /// * `last_input_word` - the last word to hash. + /// * `last_input_num_bytes` - the number of bytes in the last word. + /// + /// # Returns + /// The SHA-256 of the provided data. + fn compute_sha256_u32_array( + self: @T, input: Array, last_input_word: u32, last_input_num_bytes: u32 + ) -> [ + u32 + ; 8]; + + // DEPRECATED + fn verify_eth_signature( + self: @T, msg_hash: u256, signature: Signature, eth_address: EthAddress + ); + + /// Recovers the Ethereum address from a message hash and a signature. + /// + /// # Arguments + /// + /// * `msg_hash` - The hash of the message. + /// * `signature` - The signature to recover the address from. + /// + /// # Returns + /// A tuple containing: + /// * A boolean indicating whether the recovery was successful. + /// * The recovered Ethereum address. + fn recover_eth_address(self: @T, msg_hash: u256, signature: Signature) -> (bool, EthAddress); + + /// Performs signature verification in the secp256r1 elliptic curve. + /// + /// # Arguments + /// + /// * `msg_hash` - The hash of the message. + /// * `r` - The r component of the signature. + /// * `s` - The s component of the signature. + /// * `x` - The x coordinate of the public key. + /// * `y` - The y coordinate of the public key. + /// + /// # Returns + /// A boolean indicating whether the signature is valid. + fn verify_signature_secp256r1( + self: @T, msg_hash: u256, r: u256, s: u256, x: u256, y: u256 + ) -> bool; +} + + +pub mod embeddable_impls { + use core::keccak::{cairo_keccak, keccak_u256s_be_inputs}; + use core::num::traits::Zero; + use core::starknet::EthAddress; + use core::starknet::eth_signature::{verify_eth_signature}; + use core::starknet::secp256_trait::{ + Signature, recover_public_key, Secp256PointTrait, is_valid_signature + }; + use core::starknet::secp256_trait::{Secp256Trait}; + use core::starknet::secp256k1::Secp256k1Point; + use core::starknet::secp256r1::{Secp256r1Point}; + use core::traits::Into; + use core::{starknet, starknet::SyscallResultTrait}; + use evm::errors::EVMError; + use evm::precompiles::EcAdd; + use evm::precompiles::EcMul; + use evm::precompiles::Sha256; + use utils::traits::integer::U256Trait; + + + #[starknet::embeddable] + pub impl Precompiles of super::IPrecompiles { + fn exec_precompile( + self: @TContractState, address: felt252, data: Span + ) -> (bool, u64, Span) { + let result = match address { + 0 => Result::Err(EVMError::NotImplemented), + 1 => Result::Err(EVMError::NotImplemented), + 2 => Sha256::exec(data), + 3 | 4 => Result::Err(EVMError::NotImplemented), + 5 => Result::Err(EVMError::NotImplemented), + 6 => EcAdd::exec(data), + 7 => EcMul::exec(data), + _ => Result::Err(EVMError::NotImplemented), + }; + match result { + Result::Ok((gas, output)) => (true, gas, output), + Result::Err(_) => (false, 0, [].span()) + } + } + } + + #[starknet::embeddable] + pub impl Helpers of super::IHelpers { + fn get_block_hash(self: @TContractState, block_number: u64) -> felt252 { + starknet::syscalls::get_block_hash_syscall(block_number).unwrap_syscall() + } + + fn keccak( + self: @TContractState, + mut words: Array, + last_input_word: u64, + last_input_num_bytes: usize + ) -> u256 { + cairo_keccak(ref words, last_input_word, last_input_num_bytes).reverse_endianness() + } + + fn compute_sha256_u32_array( + self: @TContractState, + input: Array, + last_input_word: u32, + last_input_num_bytes: u32 + ) -> [ + u32 + ; 8] { + core::sha256::compute_sha256_u32_array(input, last_input_word, last_input_num_bytes) + } + + // DEPRECATED + fn verify_eth_signature( + self: @TContractState, msg_hash: u256, signature: Signature, eth_address: EthAddress + ) { + verify_eth_signature(msg_hash, signature, eth_address); + } + + fn recover_eth_address( + self: @TContractState, msg_hash: u256, signature: Signature + ) -> (bool, EthAddress) { + match recover_public_key::(:msg_hash, :signature) { + Option::Some(public_key_point) => { + let (x, y) = public_key_point.get_coordinates().unwrap_syscall(); + if (x == 0 && y == 0) { + return (false, Zero::zero()); + } + // Keccak output is little endian. + let point_hash_le = keccak_u256s_be_inputs([x, y].span()); + let point_hash = u256 { + low: core::integer::u128_byte_reverse(point_hash_le.high), + high: core::integer::u128_byte_reverse(point_hash_le.low) + }; + + (true, point_hash.into()) + }, + Option::None => (false, Zero::zero()) + } + } + + fn verify_signature_secp256r1( + self: @TContractState, msg_hash: u256, r: u256, s: u256, x: u256, y: u256 + ) -> bool { + let maybe_public_key: Option = Secp256Trait::secp256_ec_new_syscall( + x, y + ) + .unwrap_syscall(); + let public_key = match maybe_public_key { + Option::Some(public_key) => public_key, + Option::None => { return false; } + }; + + return is_valid_signature(msg_hash, r, s, public_key); + } + } +} + +#[starknet::contract] +pub mod Cairo1Helpers { + #[storage] + struct Storage {} + + #[abi(embed_v0)] + pub impl Precompiles = super::embeddable_impls::Precompiles; + + #[abi(embed_v0)] + pub impl Helpers = super::embeddable_impls::Helpers; +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/components.cairo b/cairo/kakarot-ssj/crates/contracts/src/components.cairo new file mode 100644 index 000000000..5ba62d80e --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/components.cairo @@ -0,0 +1,2 @@ +pub mod ownable; +pub mod upgradeable; diff --git a/cairo/kakarot-ssj/crates/contracts/src/components/ownable.cairo b/cairo/kakarot-ssj/crates/contracts/src/components/ownable.cairo new file mode 100644 index 000000000..fc8f88db7 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/components/ownable.cairo @@ -0,0 +1,91 @@ +// This implementation of an Ownable is inspired by Openzeppelin's work on +// OpenZeppelin Contracts for Cairo available here: https://github.com/OpenZeppelin/cairo-contracts + +use core::starknet::ContractAddress; +pub mod Errors { + pub const NOT_OWNER: felt252 = 'Caller is not the owner'; + pub const ZERO_ADDRESS_CALLER: felt252 = 'Caller is the zero address'; + pub const ZERO_ADDRESS_OWNER: felt252 = 'New owner is the zero address'; +} + +#[starknet::interface] +pub trait IOwnable { + fn owner(self: @TContractState) -> ContractAddress; + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TContractState); +} + +#[starknet::component] +pub mod ownable_component { + use core::num::traits::Zero; + use core::starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use core::starknet::{get_caller_address, ContractAddress}; + use super::Errors; + + #[storage] + pub struct Storage { + pub Ownable_owner: ContractAddress + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + OwnershipTransferred: OwnershipTransferred + } + + #[derive(Drop, starknet::Event)] + pub struct OwnershipTransferred { + pub previous_owner: ContractAddress, + pub new_owner: ContractAddress, + } + + + #[embeddable_as(Ownable)] + pub impl OwnableImpl< + TContractState, +HasComponent + > of super::IOwnable> { + fn owner(self: @ComponentState) -> ContractAddress { + self.Ownable_owner.read() + } + + fn transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress + ) { + assert(!new_owner.is_zero(), Errors::ZERO_ADDRESS_OWNER); + self.assert_only_owner(); + self._transfer_ownership(new_owner); + } + + fn renounce_ownership(ref self: ComponentState) { + self.assert_only_owner(); + self._transfer_ownership(Zero::zero()); + } + } + + #[generate_trait] + pub impl InternalImpl< + TContractState, +HasComponent + > of InternalTrait { + fn initializer(ref self: ComponentState, owner: ContractAddress) { + self._transfer_ownership(owner); + } + + fn assert_only_owner(self: @ComponentState) { + let owner: ContractAddress = self.Ownable_owner.read(); + let caller: ContractAddress = get_caller_address(); + assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER); + assert(caller == owner, Errors::NOT_OWNER); + } + + fn _transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress + ) { + let previous_owner: ContractAddress = self.Ownable_owner.read(); + self.Ownable_owner.write(new_owner); + self + .emit( + OwnershipTransferred { previous_owner: previous_owner, new_owner: new_owner } + ); + } + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/components/upgradeable.cairo b/cairo/kakarot-ssj/crates/contracts/src/components/upgradeable.cairo new file mode 100644 index 000000000..67c441ef5 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/components/upgradeable.cairo @@ -0,0 +1,40 @@ +use core::starknet::ClassHash; + +#[starknet::interface] +pub trait IUpgradeable { + fn upgrade_contract(ref self: TContractState, new_class_hash: ClassHash); +} + + +#[starknet::component] +pub mod upgradeable_component { + use core::starknet::ClassHash; + use core::starknet::syscalls::{replace_class_syscall}; + + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + ContractUpgraded: ContractUpgraded + } + + #[derive(Drop, starknet::Event)] + struct ContractUpgraded { + new_class_hash: ClassHash + } + + #[embeddable_as(Upgradeable)] + pub impl UpgradeableImpl< + TContractState, +HasComponent + > of super::IUpgradeable> { + fn upgrade_contract( + ref self: ComponentState, new_class_hash: starknet::ClassHash + ) { + replace_class_syscall(new_class_hash).expect('replace class failed'); + self.emit(ContractUpgraded { new_class_hash: new_class_hash }); + } + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/errors.cairo b/cairo/kakarot-ssj/crates/contracts/src/errors.cairo new file mode 100644 index 000000000..22b37eeee --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/errors.cairo @@ -0,0 +1,49 @@ +pub const BYTECODE_READ_ERROR: felt252 = 'CA: Bytecode Read Error'; +pub const BYTECODE_WRITE_ERROR: felt252 = 'CA: Bytecode Write Error'; + +pub const STORAGE_READ_ERROR: felt252 = 'CA: Storage Read Error'; +pub const STORAGE_WRITE_ERROR: felt252 = 'CA: Storage Write Error'; + +pub const NONCE_READ_ERROR: felt252 = 'CA: Nonce Read Error'; +pub const NONCE_WRITE_ERROR: felt252 = 'CA: Nonce Write Error'; + +pub const KAKAROT_VALIDATION_FAILED: [ + u8 + ; 30] = [ + 'K', + 'a', + 'k', + 'a', + 'r', + 'o', + 't', + ':', + ' ', + 'e', + 't', + 'h', + ' ', + 'v', + 'a', + 'l', + 'i', + 'd', + 'a', + 't', + 'i', + 'o', + 'n', + ' ', + 'f', + 'a', + 'i', + 'l', + 'e', + 'd' +]; + +pub const KAKAROT_REENTRANCY: [ + felt252 + ; 19] = [ + 'K', 'a', 'k', 'a', 'r', 'o', 't', ':', ' ', 'r', 'e', 'e', 'n', 't', 'r', 'a', 'n', 'c', 'y' +]; diff --git a/cairo/kakarot-ssj/crates/contracts/src/kakarot_core.cairo b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core.cairo new file mode 100644 index 000000000..0989f1244 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core.cairo @@ -0,0 +1,8 @@ +pub mod eth_rpc; +pub mod interface; +mod kakarot; +pub use interface::{ + IKakarotCore, IKakarotCoreDispatcher, IKakarotCoreDispatcherTrait, + IExtendedKakarotCoreDispatcher, IExtendedKakarotCoreDispatcherTrait +}; +pub use kakarot::KakarotCore; diff --git a/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/eth_rpc.cairo b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/eth_rpc.cairo new file mode 100644 index 000000000..b5259b65d --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/eth_rpc.cairo @@ -0,0 +1,362 @@ +use core::num::traits::Zero; +use core::starknet::get_tx_info; +use core::starknet::{EthAddress, get_caller_address, ContractAddress}; +use crate::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; +use crate::kakarot_core::interface::IKakarotCore; +use crate::kakarot_core::kakarot::{KakarotCore, KakarotCore::{KakarotCoreState}}; +use evm::backend::starknet_backend; +use evm::backend::validation::validate_eth_tx; +use evm::model::account::AccountTrait; +use evm::model::{TransactionResult, Address}; +use evm::{EVMTrait}; +use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; +use utils::constants::POW_2_53; +use utils::eth_transaction::transaction::{Transaction, TransactionTrait}; + +#[starknet::interface] +pub trait IEthRPC { + /// Returns the balance of the specified address. + /// + /// This is a view-only function that doesn't modify the state. + /// + /// # Arguments + /// + /// * `address` - The Ethereum address to get the balance from + /// + /// # Returns + /// + /// The balance of the address as a u256 + fn eth_get_balance(self: @T, address: EthAddress) -> u256; + + /// Returns the number of transactions sent from the specified address. + /// + /// This is a view-only function that doesn't modify the state. + /// + /// # Arguments + /// + /// * `address` - The Ethereum address to get the transaction count from + /// + /// # Returns + /// + /// The transaction count of the address as a u64 + fn eth_get_transaction_count(self: @T, address: EthAddress) -> u64; + + /// Returns the current chain ID. + /// + /// This is a view-only function that doesn't modify the state. + /// + /// # Returns + /// + /// The chain ID as a u64 + fn eth_chain_id(self: @T) -> u64; + + /// Executes a new message call immediately without creating a transaction on the block chain. + /// + /// This is a view-only function that doesn't modify the state. + /// + /// # Arguments + /// + /// * `origin` - The address the transaction is sent from + /// * `tx` - The transaction object + /// + /// # Returns + /// + /// A tuple containing: + /// * A boolean indicating success + /// * The return data as a Span + /// * The amount of gas used as a u64 + fn eth_call(self: @T, origin: EthAddress, tx: Transaction) -> (bool, Span, u64); + + /// Generates and returns an estimate of how much gas is necessary to allow the transaction to + /// complete. + /// + /// This is a view-only function that doesn't modify the state. + /// + /// # Arguments + /// + /// * `origin` - The address the transaction is sent from + /// * `tx` - The transaction object + /// + /// # Returns + /// + /// A tuple containing: + /// * A boolean indicating success + /// * The return data as a Span + /// * The estimated gas as a u64 + fn eth_estimate_gas(self: @T, origin: EthAddress, tx: Transaction) -> (bool, Span, u64); + + + /// Executes an unsigned transaction. + /// + /// This is a modified version of the eth_sendRawTransaction function. + /// Signature validation should be done before calling this function. + /// + /// # Arguments + /// + /// * `tx_data` - The unsigned transaction data as a Span + /// + /// # Returns + /// + /// A tuple containing: + /// * A boolean indicating success + /// * The return data as a Span + /// * The amount of gas used as a u64 + fn eth_send_raw_unsigned_tx(ref self: T, tx_data: Span) -> (bool, Span, u64); +} + + +#[starknet::embeddable] +pub impl EthRPC< + TContractState, impl KakarotState: KakarotCoreState, +Drop +> of IEthRPC { + fn eth_get_balance(self: @TContractState, address: EthAddress) -> u256 { + let kakarot_state = KakarotState::get_state(); + let starknet_address = kakarot_state.get_starknet_address(address); + let native_token_address = kakarot_state.get_native_token(); + let native_token = IERC20CamelDispatcher { contract_address: native_token_address }; + native_token.balanceOf(starknet_address) + } + + fn eth_get_transaction_count(self: @TContractState, address: EthAddress) -> u64 { + let kakarot_state = KakarotState::get_state(); + let starknet_address = kakarot_state.get_starknet_address(address); + let account = IAccountDispatcher { contract_address: starknet_address }; + let nonce = account.get_nonce(); + nonce + } + + fn eth_chain_id(self: @TContractState) -> u64 { + let tx_info = get_tx_info().unbox(); + let tx_chain_id: u64 = tx_info.chain_id.try_into().unwrap(); + tx_chain_id % POW_2_53.try_into().unwrap() + } + + fn eth_call( + self: @TContractState, origin: EthAddress, tx: Transaction + ) -> (bool, Span, u64) { + let mut kakarot_state = KakarotState::get_state(); + if !is_view(@kakarot_state) { + core::panic_with_felt252('fn must be called, not invoked'); + }; + + let origin = Address { evm: origin, starknet: kakarot_state.get_starknet_address(origin) }; + + let TransactionResult { success, return_data, gas_used, state: _state } = + EVMTrait::process_transaction( + ref kakarot_state, origin, tx, 0 + ); + + (success, return_data, gas_used) + } + + fn eth_estimate_gas( + self: @TContractState, origin: EthAddress, tx: Transaction + ) -> (bool, Span, u64) { + panic!("unimplemented") + } + + //TODO: we can't really unit-test this with foundry because we can't generate the RLP-encoding + //in Cairo Find another way - perhaps test-data gen with python? + fn eth_send_raw_unsigned_tx( + ref self: TContractState, mut tx_data: Span + ) -> (bool, Span, u64) { + let tx = TransactionTrait::decode_enveloped(ref tx_data).expect('EOA: could not decode tx'); + EthRPCInternal::eth_send_transaction(ref self, tx) + } +} + +trait EthRPCInternal { + /// Executes a transaction and possibly modifies the state. + /// + /// This function implements the `eth_sendTransaction` method as described in the Ethereum + /// JSON-RPC specification. + /// The nonce is taken from the corresponding account contract. + /// + /// # Arguments + /// + /// * `tx` - A `Transaction` struct + /// + /// # Returns + /// + /// A tuple containing: + /// * A boolean indicating success (TRUE if the transaction succeeded, FALSE otherwise) + /// * The return data as a `Span` + /// * The amount of gas used by the transaction as a `u64` + fn eth_send_transaction(ref self: T, tx: Transaction) -> (bool, Span, u64); +} + +impl EthRPCInternalImpl< + TContractState, impl KakarotState: KakarotCoreState, +Drop +> of EthRPCInternal { + fn eth_send_transaction(ref self: TContractState, tx: Transaction) -> (bool, Span, u64) { + let mut kakarot_state = KakarotState::get_state(); + let intrinsic_gas = validate_eth_tx(@kakarot_state, tx); + + let starknet_caller_address = get_caller_address(); + // panics if the caller is a spoofer of an EVM address. + //TODO: e2e test this! :) Send a transaction from an account that is not Kakarot's account + //(e.g. deploy an account but not from Kakarot) + let origin_evm_address = safe_get_evm_address(@self, starknet_caller_address); + let origin = Address { evm: origin_evm_address, starknet: starknet_caller_address }; + + let TransactionResult { success, return_data, gas_used, mut state } = + EVMTrait::process_transaction( + ref kakarot_state, origin, tx, intrinsic_gas + ); + starknet_backend::commit(ref state).expect('Committing state failed'); + (success, return_data, gas_used) + } +} + + +/// Returns the EVM address associated with a Starknet account deployed by Kakarot. +/// +/// This function prevents cases where a Starknet account has an entrypoint `get_evm_address()` +/// but isn't part of the Kakarot system. It also mitigates re-entrancy risk with the Cairo Interop +/// module. +/// +/// # Arguments +/// +/// * `starknet_address` - The Starknet address of the account +/// +/// # Returns +/// +/// * `EthAddress` - The associated EVM address +/// +/// # Panics +/// +/// Panics if the declared corresponding EVM address (retrieved with `get_evm_address`) +/// does not recompute into the actual caller address. +fn safe_get_evm_address< + TContractState, impl KakarotState: KakarotCoreState, +Drop +>( + self: @TContractState, starknet_address: ContractAddress +) -> EthAddress { + let account = IAccountDispatcher { contract_address: starknet_address }; + let evm_address = account.get_evm_address(); + let safe_starknet_address = AccountTrait::get_starknet_address(evm_address); + assert!( + safe_starknet_address == starknet_address, + "Kakarot: caller contract is not a Kakarot Account" + ); + evm_address +} + +fn is_view(self: @KakarotCore::ContractState) -> bool { + let tx_info = get_tx_info().unbox(); + + // If the account that originated the transaction is not zero, this means we + // are in an invoke transaction instead of a call; therefore, `eth_call` is being + // wrongly called For invoke transactions, `eth_send_transaction` must be used + if !tx_info.account_contract_address.is_zero() { + return false; + } + true +} + +#[cfg(test)] +mod tests { + use core::ops::DerefMut; + use core::starknet::EthAddress; + use core::starknet::storage::{StoragePathEntry, StoragePointerWriteAccess}; + use crate::kakarot_core::KakarotCore; + use crate::kakarot_core::eth_rpc::IEthRPC; + use crate::kakarot_core::interface::{IKakarotCore, IExtendedKakarotCoreDispatcherTrait}; + use crate::test_utils::{setup_contracts_for_testing, fund_account_with_native_token}; + use evm::test_utils::{sequencer_evm_address, evm_address, uninitialized_account}; + use snforge_std::{ + start_mock_call, start_cheat_chain_id_global, stop_cheat_chain_id_global, test_address + }; + use super::safe_get_evm_address; + use utils::constants::POW_2_53; + + fn set_up() -> KakarotCore::ContractState { + // Define the kakarot state to access contract functions + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + + kakarot_state + } + + fn tear_down() { + stop_cheat_chain_id_global(); + } + + #[test] + fn test_eth_get_transaction_count() { + let kakarot_state = set_up(); + let starknet_address = kakarot_state.get_starknet_address(evm_address()); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + assert_eq!(kakarot_state.eth_get_transaction_count(evm_address()), 1); + } + + #[test] + fn test_eth_get_balance() { + let (native_token, kakarot_core) = setup_contracts_for_testing(); + // Uninitialized accounts should return a zero balance + assert_eq!(kakarot_core.eth_get_balance(evm_address()), 0); + let sequencer_starknet_address = kakarot_core.get_starknet_address(sequencer_evm_address()); + // Fund an initialized account and make sure the balance is correct + fund_account_with_native_token(sequencer_starknet_address, native_token, 0x1); + assert_eq!(kakarot_core.eth_get_balance(sequencer_evm_address()), 0x1); + } + + #[test] + fn test_eth_chain_id_returns_input_when_less_than_pow_2_53() { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + // Convert POW_2_53 - 1 to u64 since POW_2_53 is defined as u128 + let chain_id: u64 = (POW_2_53 - 1).try_into().unwrap(); + start_cheat_chain_id_global(chain_id.into()); + assert_eq!( + kakarot_state.eth_chain_id(), + chain_id, + "Should return original chain ID when below 2^53" + ); + tear_down(); + } + + #[test] + fn test_eth_chain_id_returns_modulo_when_greater_than_or_equal_to_pow_2_53() { + // Test with a value equal to 2^53 + let kakarot_state = set_up(); + let chain_id: u64 = POW_2_53.try_into().unwrap(); + start_cheat_chain_id_global(chain_id.into()); + assert_eq!(kakarot_state.eth_chain_id(), 0, "Should return 0 when chain ID is 2^53"); + + // Test with a value greater than 2^53 + let chain_id: u64 = (POW_2_53 + 53).try_into().unwrap(); + start_cheat_chain_id_global(chain_id.into()); + assert_eq!( + kakarot_state.eth_chain_id(), 53, "Should return correct value after modulo operation" + ); + tear_down(); + } + + #[test] + fn test_safe_get_evm_address_succeeds() { + let kakarot_state = set_up(); + // no registry - returns the computed address + let starknet_address = kakarot_state.get_starknet_address(evm_address()); + start_mock_call::< + EthAddress + >(starknet_address, selector!("get_evm_address"), evm_address()); + let safe_evm_address = safe_get_evm_address(@kakarot_state, starknet_address); + assert_eq!(safe_evm_address, evm_address()); + } + + #[test] + #[should_panic(expected: "Kakarot: caller contract is not a Kakarot Account")] + fn test_safe_get_evm_address_panics_when_caller_is_not_kakarot_account() { + let mut kakarot_state = set_up(); + let mut kakarot_storage = kakarot_state.deref_mut(); + + // Calling get_evm_address() on a fake starknet account that will return `evm_address()`. + // Then, when computing the deterministic starknet_address with get_starknet_address(), it + // will return a different address. + // This should fail. + let fake_starknet_account = 'fake_account'.try_into().unwrap(); + start_mock_call::< + EthAddress + >(fake_starknet_account, selector!("get_evm_address"), evm_address()); + safe_get_evm_address(@kakarot_state, fake_starknet_account); + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/interface.cairo b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/interface.cairo new file mode 100644 index 000000000..e0fcdb1a8 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/interface.cairo @@ -0,0 +1,127 @@ +use core::starknet::{ContractAddress, EthAddress, ClassHash}; +use utils::eth_transaction::transaction::Transaction; + +#[starknet::interface] +pub trait IKakarotCore { + /// Sets the native token, this token will be considered the native coin in the Ethereum sense + fn set_native_token(ref self: TContractState, native_token: ContractAddress); + + /// Gets the native token used by the Kakarot smart contract + fn get_native_token(self: @TContractState) -> ContractAddress; + + /// Checks into KakarotCore storage if an EOA or a CA has been deployed for + /// a particular EVM address and. If so returns its corresponding address, + /// otherwise returns 0 + fn address_registry(self: @TContractState, evm_address: EthAddress) -> ContractAddress; + + /// Deploys an EOA for a particular EVM address + fn deploy_externally_owned_account( + ref self: TContractState, evm_address: EthAddress + ) -> ContractAddress; + + /// Upgrade the KakarotCore smart contract + /// Using replace_class_syscall + fn upgrade(ref self: TContractState, new_class_hash: ClassHash); + + // Setter for the Account Class Hash + fn set_account_contract_class_hash(ref self: TContractState, new_class_hash: ClassHash); + fn get_account_contract_class_hash(self: @TContractState) -> ClassHash; + + // Getter for the Generic Account Class + fn uninitialized_account_class_hash(self: @TContractState) -> ClassHash; + // Setter for the Generic Account Class + fn set_account_class_hash(ref self: TContractState, new_class_hash: ClassHash); + + fn register_account(ref self: TContractState, evm_address: EthAddress); + + // Getter for the Block Gas Limit + fn get_block_gas_limit(self: @TContractState) -> u64; + + // Getter for the Base Fee + fn get_base_fee(self: @TContractState) -> u64; + /// Setter for the base fee + fn set_base_fee(ref self: TContractState, base_fee: u64); + + /// Returns the corresponding Starknet address for a given EVM address. + /// + /// Returns the registered address if there is one, otherwise returns the deterministic + /// address got when Kakarot deploys an account. + /// + /// # Arguments + /// + /// * `evm_address` - The EVM address to transform to a starknet address + /// + /// # Returns + /// + /// * `ContractAddress` - The Starknet Account Contract address + fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress; +} + +#[starknet::interface] +pub trait IExtendedKakarotCore { + /// Sets the native token, this token will be considered the native coin in the Ethereum sense + fn set_native_token(ref self: TContractState, native_token: ContractAddress); + + /// Gets the native token used by the Kakarot smart contract + fn get_native_token(self: @TContractState) -> ContractAddress; + + /// Checks into KakarotCore storage if an EOA or a CA has been deployed for + /// a particular EVM address and. If so returns its corresponding address, + /// otherwise returns 0 + fn address_registry(self: @TContractState, evm_address: EthAddress) -> ContractAddress; + + /// Deploys an EOA for a particular EVM address + fn deploy_externally_owned_account( + ref self: TContractState, evm_address: EthAddress + ) -> ContractAddress; + + /// Returns the balance of the specified address. + fn eth_get_balance(self: @TContractState, address: EthAddress) -> u256; + + /// View entrypoint into the EVM + /// Performs view calls into the blockchain + /// It cannot modify the state of the chain + fn eth_call( + self: @TContractState, origin: EthAddress, tx: Transaction + ) -> (bool, Span, u64); + + /// Transaction entrypoint into the EVM + /// Executes an EVM transaction and possibly modifies the state + fn eth_send_transaction(ref self: TContractState, tx: Transaction) -> (bool, Span, u64); + + fn eth_send_raw_unsigned_tx( + ref self: TContractState, encoded_tx_data: Span + ) -> (bool, Span, u64); + + // Returns the transaction count (nonce) of the specified address + fn eth_get_transaction_count(self: @TContractState, address: EthAddress) -> u64; + + /// Upgrade the KakarotCore smart contract + /// Using replace_class_syscall + fn upgrade(ref self: TContractState, new_class_hash: ClassHash); + + // Setter for the Account Class Hash + fn set_account_contract_class_hash(ref self: TContractState, new_class_hash: ClassHash); + fn get_account_contract_class_hash(self: @TContractState) -> ClassHash; + + // Getter for the Generic Account Class + fn uninitialized_account_class_hash(self: @TContractState) -> ClassHash; + // Setter for the Generic Account Class + fn set_account_class_hash(ref self: TContractState, new_class_hash: ClassHash); + + fn register_account(ref self: TContractState, evm_address: EthAddress); + + // Getter for the Block Gas Limit + fn get_block_gas_limit(self: @TContractState) -> u64; + // Getter for the Base Fee + fn get_base_fee(self: @TContractState) -> u64; + /// Setter for the base fee + fn set_base_fee(ref self: TContractState, base_fee: u64); + + // Getter for the Starknet Address + fn get_starknet_address(self: @TContractState, evm_address: EthAddress) -> ContractAddress; + + fn owner(self: @TContractState) -> ContractAddress; + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TContractState); +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/kakarot.cairo b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/kakarot.cairo new file mode 100644 index 000000000..2b5f14355 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/kakarot_core/kakarot.cairo @@ -0,0 +1,218 @@ +const INVOKE_ETH_CALL_FORBIDDEN: felt252 = 'KKT: Cannot invoke eth_call'; + + +#[starknet::contract] +pub mod KakarotCore { + use core::num::traits::Zero; + use core::starknet::event::EventEmitter; + use core::starknet::get_caller_address; + use core::starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; + use core::starknet::{EthAddress, ContractAddress, ClassHash, get_contract_address}; + use crate::components::ownable::{ownable_component}; + use crate::components::upgradeable::{IUpgradeable, upgradeable_component}; + use crate::kakarot_core::eth_rpc; + use crate::kakarot_core::interface::IKakarotCore; + use evm::backend::starknet_backend; + use evm::model::account::AccountTrait; + use utils::helpers::compute_starknet_address; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent); + + /// STORAGE /// + + #[storage] + pub struct Storage { + pub Kakarot_evm_to_starknet_address: Map::, + pub Kakarot_uninitialized_account_class_hash: ClassHash, + pub Kakarot_account_contract_class_hash: ClassHash, + pub Kakarot_native_token_address: ContractAddress, + pub Kakarot_coinbase: EthAddress, + pub Kakarot_base_fee: u64, + pub Kakarot_prev_randao: u256, + pub Kakarot_block_gas_limit: u64, + // Components + #[substorage(v0)] + ownable: ownable_component::Storage, + #[substorage(v0)] + upgradeable: upgradeable_component::Storage, + } + + /// EVENTS /// + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + OwnableEvent: ownable_component::Event, + UpgradeableEvent: upgradeable_component::Event, + AccountDeployed: AccountDeployed, + AccountClassHashChange: AccountClassHashChange, + EOAClassHashChange: EOAClassHashChange, + } + + #[derive(Copy, Drop, starknet::Event)] + pub struct AccountDeployed { + #[key] + pub evm_address: EthAddress, + #[key] + pub starknet_address: ContractAddress, + } + + #[derive(Copy, Drop, starknet::Event)] + pub struct AccountClassHashChange { + pub old_class_hash: ClassHash, + pub new_class_hash: ClassHash, + } + + + #[derive(Copy, Drop, starknet::Event)] + pub struct EOAClassHashChange { + pub old_class_hash: ClassHash, + pub new_class_hash: ClassHash, + } + + + /// Trait bounds allowing embedded implementations to be specific to this contract + pub trait KakarotCoreState { + fn get_state() -> ContractState; + } + + impl _KakarotCoreState of KakarotCoreState { + fn get_state() -> ContractState { + unsafe_new_contract_state() + } + } + + /// CONSTRUCTOR /// + + #[constructor] + fn constructor( + ref self: ContractState, + owner: ContractAddress, + native_token: ContractAddress, + account_contract_class_hash: ClassHash, + uninitialized_account_class_hash: ClassHash, + coinbase: EthAddress, + block_gas_limit: u64, + mut eoas_to_deploy: Span, + ) { + self.ownable.initializer(owner); + self.Kakarot_native_token_address.write(native_token); + self.Kakarot_account_contract_class_hash.write(account_contract_class_hash); + self.Kakarot_uninitialized_account_class_hash.write(uninitialized_account_class_hash); + self.Kakarot_coinbase.write(coinbase); + self.Kakarot_block_gas_limit.write(block_gas_limit); + for eoa_address in eoas_to_deploy { + self.deploy_externally_owned_account(*eoa_address); + }; + } + + /// PUBLIC-FACING FUNCTIONS /// + + // Public-facing "ownable" functions + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::Ownable; + + /// Public-facing "ethereum" functions + /// Used to make EVM-related actions through Kakarot. + #[abi(embed_v0)] + pub impl EthRPCImpl = eth_rpc::EthRPC; + + + /// Public-facing "kakarot" functions + /// Used to interact with the Kakarot contract outside of EVM-related actions. + #[abi(embed_v0)] + pub impl KakarotCoreImpl of IKakarotCore { + fn set_native_token(ref self: ContractState, native_token: ContractAddress) { + self.ownable.assert_only_owner(); + self.Kakarot_native_token_address.write(native_token); + } + + fn get_native_token(self: @ContractState) -> ContractAddress { + self.Kakarot_native_token_address.read() + } + + fn address_registry(self: @ContractState, evm_address: EthAddress) -> ContractAddress { + self.Kakarot_evm_to_starknet_address.read(evm_address) + } + + fn deploy_externally_owned_account( + ref self: ContractState, evm_address: EthAddress + ) -> ContractAddress { + starknet_backend::deploy(evm_address).expect('EOA Deployment failed').starknet + } + + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade_contract(new_class_hash); + } + + fn get_account_contract_class_hash(self: @ContractState) -> ClassHash { + self.Kakarot_account_contract_class_hash.read() + } + + fn set_account_contract_class_hash(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + let old_class_hash = self.Kakarot_account_contract_class_hash.read(); + self.Kakarot_account_contract_class_hash.write(new_class_hash); + self.emit(EOAClassHashChange { old_class_hash, new_class_hash }); + } + + fn uninitialized_account_class_hash(self: @ContractState) -> ClassHash { + self.Kakarot_uninitialized_account_class_hash.read() + } + + fn set_account_class_hash(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + let old_class_hash = self.Kakarot_uninitialized_account_class_hash.read(); + self.Kakarot_uninitialized_account_class_hash.write(new_class_hash); + self.emit(AccountClassHashChange { old_class_hash, new_class_hash }); + } + + fn register_account(ref self: ContractState, evm_address: EthAddress) { + let existing_address = self.Kakarot_evm_to_starknet_address.read(evm_address); + assert(existing_address.is_zero(), 'Account already exists'); + + let starknet_address = compute_starknet_address( + get_contract_address(), + evm_address, + self.Kakarot_uninitialized_account_class_hash.read() + ); + assert!( + starknet_address == get_caller_address(), "Account must be registered by the caller" + ); + + self.Kakarot_evm_to_starknet_address.write(evm_address, starknet_address); + self.emit(AccountDeployed { evm_address, starknet_address }); + } + + fn get_block_gas_limit(self: @ContractState) -> u64 { + self.Kakarot_block_gas_limit.read() + } + + fn set_base_fee(ref self: ContractState, base_fee: u64) { + self.ownable.assert_only_owner(); + self.Kakarot_base_fee.write(base_fee); + } + + fn get_base_fee(self: @ContractState) -> u64 { + self.Kakarot_base_fee.read() + } + + + fn get_starknet_address(self: @ContractState, evm_address: EthAddress) -> ContractAddress { + AccountTrait::get_starknet_address(evm_address) + } + } + + /// INTERNAL-FACING FUNCTIONS /// + + // Internal-facing "ownable" functions + impl OwnableInternalImpl = ownable_component::InternalImpl; + + // Internal-facing "upgradeable" functions + impl UpgradeableImpl = upgradeable_component::Upgradeable; +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/lib.cairo b/cairo/kakarot-ssj/crates/contracts/src/lib.cairo new file mode 100644 index 000000000..995015b10 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/lib.cairo @@ -0,0 +1,37 @@ +pub mod account_contract; +pub mod cairo1_helpers; +pub mod components; + +pub mod errors; + +// Kakarot smart contract +pub mod kakarot_core; +pub mod storage; + +#[cfg(target: 'test')] +pub mod test_data; + +#[cfg(target: 'test')] +pub mod test_utils; + +// Account transparent proxy +mod uninitialized_account; +pub use account_contract::{AccountContract, IAccount, IAccountDispatcher, IAccountDispatcherTrait}; +pub use cairo1_helpers::{ + Cairo1Helpers, IPrecompiles, IHelpers, IPrecompilesDispatcher, IHelpersDispatcher, + IPrecompilesDispatcherTrait, IHelpersDispatcherTrait +}; +pub use kakarot_core::{ + KakarotCore, IKakarotCore, IKakarotCoreDispatcher, IKakarotCoreDispatcherTrait, + IExtendedKakarotCoreDispatcher, IExtendedKakarotCoreDispatcherTrait +}; +pub use uninitialized_account::{UninitializedAccount}; + +//TODO: hide this behind a feature flag +pub mod test_contracts { + pub mod test_upgradeable; +} + +pub mod mocks { + pub mod cairo1_helpers_fixture; +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/mocks/cairo1_helpers_fixture.cairo b/cairo/kakarot-ssj/crates/contracts/src/mocks/cairo1_helpers_fixture.cairo new file mode 100644 index 000000000..1a00d76b5 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/mocks/cairo1_helpers_fixture.cairo @@ -0,0 +1,15 @@ +#[starknet::contract] +pub mod Cairo1HelpersFixture { + use crate::cairo1_helpers::embeddable_impls; + + const VERSION: felt252 = 2; + + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl Precompiles = embeddable_impls::Precompiles; + + #[abi(embed_v0)] + impl Helpers = embeddable_impls::Helpers; +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/storage.cairo b/cairo/kakarot-ssj/crates/contracts/src/storage.cairo new file mode 100644 index 000000000..3f047b1a8 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/storage.cairo @@ -0,0 +1,199 @@ +use core::ops::DerefMut; +use core::ops::SnapshotDeref; +use core::starknet::storage::{ + StoragePointerReadAccess, StoragePointerWriteAccess, StorageTrait, StorageTraitMut +}; +use core::starknet::storage_access::StorageBaseAddress; +use core::starknet::syscalls::{storage_read_syscall, storage_write_syscall}; +use core::starknet::{SyscallResult, Store, StorageAddress}; +use crate::account_contract::AccountContract::unsafe_new_contract_state as account_contract_state; +use utils::utils::{pack_bytes, load_packed_bytes}; + +/// A wrapper type for the bytecode storage. Packing / unpacking is done transparently inside the +/// `read` and `write` methods of `Store`. +#[derive(Copy, Drop)] +pub struct StorageBytecode { + pub bytecode: Span +} + +const BYTES_PER_FELT: NonZero = 31; + +/// An implementation of the `Store` trait for our specific `StorageBytecode` type. +/// The packing-unpacking is done inside the `read` and `write` methods, thus transparent to the +/// user. +/// The bytecode is stored sequentially, starting from storage address 0, for compatibility purposes +/// with KakarotZero. +/// The bytecode length is stored in the `Account_bytecode_len` storage variable, which is accessed +/// by the `read` and `write` methods. +impl StoreBytecode of Store { + /// Side effect: reads the bytecode len from the Account_bytecode_len storage variable + fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult { + // Read the bytecode len from the storage of the current contract + let state = account_contract_state(); + let bytecode_len: u32 = state.snapshot_deref().storage().Account_bytecode_len.read(); + let (chunks_count, _remainder) = DivRem::div_rem(bytecode_len, BYTES_PER_FELT); + + // Read the bytecode from the storage of the current contract, starting from address 0. + //TODO(opti): unpack chunks directly instead of reading them one by one and unpacking them + // afterwards. + let base: felt252 = 0; + let mut packed_bytecode = array![]; + for i in 0 + ..chunks_count + + 1 { + let storage_address: StorageAddress = (base + i.into()).try_into().unwrap(); + let chunk = storage_read_syscall(address_domain, storage_address).unwrap(); + packed_bytecode.append(chunk); + }; + let bytecode = load_packed_bytes(packed_bytecode.span(), bytecode_len); + SyscallResult::Ok(StorageBytecode { bytecode: bytecode.span() }) + } + + /// Side effect: Writes the bytecode len to the Account_bytecode_len storage variable + fn write( + address_domain: u32, base: StorageBaseAddress, value: StorageBytecode + ) -> SyscallResult<()> { + let base: felt252 = 0; + let mut state = account_contract_state(); + let bytecode_len: u32 = value.bytecode.len(); + state.deref_mut().storage_mut().Account_bytecode_len.write(bytecode_len); + + let mut packed_bytecode = pack_bytes(value.bytecode); + let mut i = 0; + for chunk in packed_bytecode { + let storage_address: StorageAddress = (base + i.into()).try_into().unwrap(); + storage_write_syscall(address_domain, storage_address, chunk).unwrap(); + i += 1; + }; + SyscallResult::Ok(()) + } + + fn read_at_offset( + address_domain: u32, base: StorageBaseAddress, offset: u8 + ) -> SyscallResult { + panic!("'read_at_offset' is not implemented for StoreBytecode") + } + + fn write_at_offset( + address_domain: u32, base: StorageBaseAddress, offset: u8, value: StorageBytecode + ) -> SyscallResult<()> { + panic!("'write_at_offset' is not implemented for StoreBytecode") + } + + fn size() -> u8 { + panic!("'size' is not implemented for StoreBytecode") + } +} + +#[cfg(test)] +mod tests { + use core::starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use crate::account_contract::AccountContract::unsafe_new_contract_state as account_contract_state; + use starknet::storage_access::Store; + use starknet::storage_access::{ + StorageBaseAddress, StorageAddress, storage_base_address_from_felt252 + }; + use starknet::syscalls::storage_read_syscall; + use super::DerefMut; + use super::SnapshotDeref; + use super::StorageBytecode; + use super::StorageTrait; + use super::StorageTraitMut; + use utils::utils::pack_bytes; + + #[test] + fn test_store_bytecode_empty() { + let mut state = account_contract_state(); + let bytecode = [].span(); + // Write the bytecode to the storage + state.deref_mut().storage_mut().Account_bytecode.write(StorageBytecode { bytecode }); + // Verify that the bytecode was written correctly and the len as well + let bytecode_len = state.snapshot_deref().storage().Account_bytecode_len.read(); + let stored_bytecode = state.snapshot_deref().storage().Account_bytecode.read(); + assert_eq!(bytecode_len, bytecode.len()); + assert_eq!(stored_bytecode.bytecode, bytecode); + } + + #[test] + fn test_store_bytecode_single_chunk() { + let mut state = account_contract_state(); + let bytecode = [0, 1, 2, 3, 4, 5].span(); + // Write the bytecode to the storage + state.deref_mut().storage_mut().Account_bytecode.write(StorageBytecode { bytecode }); + // Verify that the bytecode was written correctly and the len as well + let bytecode_len = state.snapshot_deref().storage().Account_bytecode_len.read(); + let stored_bytecode = state.snapshot_deref().storage().Account_bytecode.read(); + assert_eq!(bytecode_len, bytecode.len()); + assert_eq!(stored_bytecode.bytecode, bytecode); + } + + #[test] + fn test_store_bytecode_multiple_chunks() { + let mut state = account_contract_state(); + let mut bytecode_array = array![]; + for i in 0..100_u8 { + bytecode_array.append(i); + }; + let bytecode = bytecode_array.span(); + // Write the bytecode to the storage + state.deref_mut().storage_mut().Account_bytecode.write(StorageBytecode { bytecode }); + // Verify that the bytecode was written correctly and the len as well + let bytecode_len = state.snapshot_deref().storage().Account_bytecode_len.read(); + let stored_bytecode = state.snapshot_deref().storage().Account_bytecode.read(); + assert_eq!(bytecode_len, bytecode.len()); + assert_eq!(stored_bytecode.bytecode, bytecode); + } + + #[test] + fn test_store_bytecode_partial_chunk() { + let mut state = account_contract_state(); + let bytecode = [ + 1 + ; 33].span(); // 33 bytes will require 2 chunks, with the second chunk partially filled + // Write the bytecode to the storage + state.deref_mut().storage_mut().Account_bytecode.write(StorageBytecode { bytecode }); + // Verify that the bytecode was written correctly and the len as well + let bytecode_len = state.snapshot_deref().storage().Account_bytecode_len.read(); + let stored_bytecode = state.snapshot_deref().storage().Account_bytecode.read(); + assert_eq!(bytecode_len, bytecode.len()); + assert_eq!(stored_bytecode.bytecode, bytecode); + } + + #[test] + fn test_storage_layout_sequential_from_zero() { + let base_address: StorageAddress = 0.try_into().unwrap(); + let bytecode = [0x12; 33].span(); + let stored_bytecode = StorageBytecode { bytecode }; + let mut state = account_contract_state(); + state.deref_mut().storage_mut().Account_bytecode.write(stored_bytecode); + + // Verify that the bytecode was packed in chunks sequential from zero + let chunk0 = storage_read_syscall(0, base_address).unwrap(); + let chunk1 = storage_read_syscall(0, 1.try_into().unwrap()).unwrap(); + + assert_eq!(chunk0, (*pack_bytes([0x12; 31].span())[0])); + assert_eq!(chunk1, 0x1212); + } + + #[test] + #[should_panic(expected: "'read_at_offset' is not implemented for StoreBytecode")] + fn test_read_at_offset_panics() { + let base_address: StorageBaseAddress = storage_base_address_from_felt252(0); + let _ = Store::::read_at_offset(0, base_address, 0); + } + + #[test] + #[should_panic(expected: "'write_at_offset' is not implemented for StoreBytecode")] + fn test_write_at_offset_panics() { + let base_address: StorageBaseAddress = storage_base_address_from_felt252(0); + let bytecode = array![].span(); + let stored_bytecode = StorageBytecode { bytecode }; + let _ = Store::::write_at_offset(0, base_address, 0, stored_bytecode); + } + + #[test] + #[should_panic(expected: "'size' is not implemented for StoreBytecode")] + fn test_size_panics() { + let _ = Store::::size(); + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/test_contracts/test_upgradeable.cairo b/cairo/kakarot-ssj/crates/contracts/src/test_contracts/test_upgradeable.cairo new file mode 100644 index 000000000..22cf19587 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/test_contracts/test_upgradeable.cairo @@ -0,0 +1,102 @@ +use crate::components::upgradeable::{upgradeable_component}; + +use upgradeable_component::{UpgradeableImpl}; + +#[starknet::interface] +pub trait IMockContractUpgradeable { + fn version(self: @TContractState) -> felt252; +} + +#[starknet::contract] +pub mod MockContractUpgradeableV0 { + use crate::components::upgradeable::{upgradeable_component}; + use super::IMockContractUpgradeable; + component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent); + + #[abi(embed_v0)] + impl UpgradeableImpl = upgradeable_component::Upgradeable; + + #[storage] + struct Storage { + #[substorage(v0)] + upgradeable: upgradeable_component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + UpgradeableEvent: upgradeable_component::Event + } + + #[abi(embed_v0)] + impl MockContractUpgradeableImpl of IMockContractUpgradeable { + fn version(self: @ContractState) -> felt252 { + 0 + } + } +} + +#[starknet::contract] +pub mod MockContractUpgradeableV1 { + use crate::components::upgradeable::{upgradeable_component}; + use super::IMockContractUpgradeable; + component!(path: upgradeable_component, storage: upgradeable, event: upgradeableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + upgradeable: upgradeable_component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + upgradeableEvent: upgradeable_component::Event + } + + #[abi(embed_v0)] + impl MockContractUpgradeableImpl of IMockContractUpgradeable { + fn version(self: @ContractState) -> felt252 { + 1 + } + } +} + +#[cfg(test)] +mod tests { + use core::starknet::syscalls::{deploy_syscall}; + use crate::components::upgradeable::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; + use snforge_std::{declare, DeclareResultTrait}; + use starknet::{ClassHash}; + use super::{IMockContractUpgradeableDispatcher, IMockContractUpgradeableDispatcherTrait}; + + #[test] + fn test_upgradeable_update_contract() { + let mock_contract_upgradeable_v0_class_hash = (*declare("MockContractUpgradeableV0") + .unwrap() + .contract_class() + .class_hash); + let (contract_address, _) = deploy_syscall( + mock_contract_upgradeable_v0_class_hash, 0, [].span(), false + ) + .unwrap(); + + let version = IMockContractUpgradeableDispatcher { contract_address: contract_address } + .version(); + + assert(version == 0, 'version is not 0'); + + let mock_contract_upgradeable_v1_class_hash = (*declare("MockContractUpgradeableV1") + .unwrap() + .contract_class() + .class_hash); + let new_class_hash: ClassHash = mock_contract_upgradeable_v1_class_hash; + + IUpgradeableDispatcher { contract_address: contract_address } + .upgrade_contract(new_class_hash); + + let version = IMockContractUpgradeableDispatcher { contract_address: contract_address } + .version(); + assert(version == 1, 'version is not 1'); + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/test_data.cairo b/cairo/kakarot-ssj/crates/contracts/src/test_data.cairo new file mode 100644 index 000000000..b56a08684 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/test_data.cairo @@ -0,0 +1,1781 @@ +// Counter Smart Contract Bytecode: +// 0.8.18+commit.87f61d96 +// with optimisation enabled (depth 200) (remix.ethereum.org) +// SPDX-License-Identifier: MIT +// pragma solidity >=0.7.0 <0.9.0; + +// contract Counter { +// uint public count; + +// // Function to get the current count +// function get() public view returns (uint) { +// return count; +// } + +// // Function to increment count by 1 +// function inc() public { +// count += 1; +// } + +// // Function to decrement count by 1 +// function dec() public { +// // This function will fail if count = 0 +// count -= 1; +// } +// } + +pub fn deploy_counter_calldata() -> Span { + [ + 0x60, + 0x80, + 0x60, + 0x40, + 0x52, + 0x34, + 0x80, + 0x15, + 0x61, + 0x00, + 0x0f, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x61, + 0x01, + 0xd9, + 0x80, + 0x61, + 0x00, + 0x1d, + 0x5f, + 0x39, + 0x5f, + 0xf3, + 0xfe, + 0x60, + 0x80, + 0x60, + 0x40, + 0x52, + 0x34, + 0x80, + 0x15, + 0x61, + 0x00, + 0x0f, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x60, + 0x04, + 0x36, + 0x10, + 0x61, + 0x00, + 0x4a, + 0x57, + 0x5f, + 0x35, + 0x60, + 0xe0, + 0x1c, + 0x80, + 0x63, + 0x06, + 0x66, + 0x1a, + 0xbd, + 0x14, + 0x61, + 0x00, + 0x4e, + 0x57, + 0x80, + 0x63, + 0x37, + 0x13, + 0x03, + 0xc0, + 0x14, + 0x61, + 0x00, + 0x6c, + 0x57, + 0x80, + 0x63, + 0x6d, + 0x4c, + 0xe6, + 0x3c, + 0x14, + 0x61, + 0x00, + 0x76, + 0x57, + 0x80, + 0x63, + 0xb3, + 0xbc, + 0xfa, + 0x82, + 0x14, + 0x61, + 0x00, + 0x94, + 0x57, + 0x5b, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x61, + 0x00, + 0x56, + 0x61, + 0x00, + 0x9e, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x61, + 0x00, + 0x63, + 0x91, + 0x90, + 0x61, + 0x00, + 0xf7, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x80, + 0x91, + 0x03, + 0x90, + 0xf3, + 0x5b, + 0x61, + 0x00, + 0x74, + 0x61, + 0x00, + 0xa3, + 0x56, + 0x5b, + 0x00, + 0x5b, + 0x61, + 0x00, + 0x7e, + 0x61, + 0x00, + 0xbd, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x61, + 0x00, + 0x8b, + 0x91, + 0x90, + 0x61, + 0x00, + 0xf7, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x80, + 0x91, + 0x03, + 0x90, + 0xf3, + 0x5b, + 0x61, + 0x00, + 0x9c, + 0x61, + 0x00, + 0xc5, + 0x56, + 0x5b, + 0x00, + 0x5b, + 0x5f, + 0x54, + 0x81, + 0x56, + 0x5b, + 0x60, + 0x01, + 0x5f, + 0x80, + 0x82, + 0x82, + 0x54, + 0x61, + 0x00, + 0xb4, + 0x91, + 0x90, + 0x61, + 0x01, + 0x3d, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x50, + 0x81, + 0x90, + 0x55, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x80, + 0x54, + 0x90, + 0x50, + 0x90, + 0x56, + 0x5b, + 0x60, + 0x01, + 0x5f, + 0x80, + 0x82, + 0x82, + 0x54, + 0x61, + 0x00, + 0xd6, + 0x91, + 0x90, + 0x61, + 0x01, + 0x70, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x50, + 0x81, + 0x90, + 0x55, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x81, + 0x90, + 0x50, + 0x91, + 0x90, + 0x50, + 0x56, + 0x5b, + 0x61, + 0x00, + 0xf1, + 0x81, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x82, + 0x52, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x60, + 0x20, + 0x82, + 0x01, + 0x90, + 0x50, + 0x61, + 0x01, + 0x0a, + 0x5f, + 0x83, + 0x01, + 0x84, + 0x61, + 0x00, + 0xe8, + 0x56, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x7f, + 0x4e, + 0x48, + 0x7b, + 0x71, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x5f, + 0x52, + 0x60, + 0x11, + 0x60, + 0x04, + 0x52, + 0x60, + 0x24, + 0x5f, + 0xfd, + 0x5b, + 0x5f, + 0x61, + 0x01, + 0x47, + 0x82, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x91, + 0x50, + 0x61, + 0x01, + 0x52, + 0x83, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x82, + 0x82, + 0x01, + 0x90, + 0x50, + 0x80, + 0x82, + 0x11, + 0x15, + 0x61, + 0x01, + 0x6a, + 0x57, + 0x61, + 0x01, + 0x69, + 0x61, + 0x01, + 0x10, + 0x56, + 0x5b, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x61, + 0x01, + 0x7a, + 0x82, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x91, + 0x50, + 0x61, + 0x01, + 0x85, + 0x83, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x82, + 0x82, + 0x03, + 0x90, + 0x50, + 0x81, + 0x81, + 0x11, + 0x15, + 0x61, + 0x01, + 0x9d, + 0x57, + 0x61, + 0x01, + 0x9c, + 0x61, + 0x01, + 0x10, + 0x56, + 0x5b, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0xfe, + 0xa2, + 0x64, + 0x69, + 0x70, + 0x66, + 0x73, + 0x58, + 0x22, + 0x12, + 0x20, + 0x7e, + 0x79, + 0x2f, + 0xcf, + 0xf2, + 0x8a, + 0x4b, + 0xf0, + 0xba, + 0xd8, + 0x67, + 0x5c, + 0x5b, + 0xc2, + 0x28, + 0x8b, + 0x07, + 0x83, + 0x5a, + 0xeb, + 0xaa, + 0x90, + 0xb8, + 0xdc, + 0x5e, + 0x0d, + 0xf1, + 0x91, + 0x83, + 0xfb, + 0x72, + 0xcf, + 0x64, + 0x73, + 0x6f, + 0x6c, + 0x63, + 0x43, + 0x00, + 0x08, + 0x16, + 0x00, + 0x33 + ].span() +} + +pub fn counter_evm_bytecode() -> Span { + [ + 0x60, + 0x80, + 0x60, + 0x40, + 0x52, + 0x34, + 0x80, + 0x15, + 0x61, + 0x00, + 0x0f, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x60, + 0x04, + 0x36, + 0x10, + 0x61, + 0x00, + 0x4a, + 0x57, + 0x5f, + 0x35, + 0x60, + 0xe0, + 0x1c, + 0x80, + 0x63, + 0x06, + 0x66, + 0x1a, + 0xbd, + 0x14, + 0x61, + 0x00, + 0x4e, + 0x57, + 0x80, + 0x63, + 0x37, + 0x13, + 0x03, + 0xc0, + 0x14, + 0x61, + 0x00, + 0x6c, + 0x57, + 0x80, + 0x63, + 0x6d, + 0x4c, + 0xe6, + 0x3c, + 0x14, + 0x61, + 0x00, + 0x76, + 0x57, + 0x80, + 0x63, + 0xb3, + 0xbc, + 0xfa, + 0x82, + 0x14, + 0x61, + 0x00, + 0x94, + 0x57, + 0x5b, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x61, + 0x00, + 0x56, + 0x61, + 0x00, + 0x9e, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x61, + 0x00, + 0x63, + 0x91, + 0x90, + 0x61, + 0x00, + 0xf7, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x80, + 0x91, + 0x03, + 0x90, + 0xf3, + 0x5b, + 0x61, + 0x00, + 0x74, + 0x61, + 0x00, + 0xa3, + 0x56, + 0x5b, + 0x00, + 0x5b, + 0x61, + 0x00, + 0x7e, + 0x61, + 0x00, + 0xbd, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x61, + 0x00, + 0x8b, + 0x91, + 0x90, + 0x61, + 0x00, + 0xf7, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x80, + 0x91, + 0x03, + 0x90, + 0xf3, + 0x5b, + 0x61, + 0x00, + 0x9c, + 0x61, + 0x00, + 0xc5, + 0x56, + 0x5b, + 0x00, + 0x5b, + 0x5f, + 0x54, + 0x81, + 0x56, + 0x5b, + 0x60, + 0x01, + 0x5f, + 0x80, + 0x82, + 0x82, + 0x54, + 0x61, + 0x00, + 0xb4, + 0x91, + 0x90, + 0x61, + 0x01, + 0x3d, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x50, + 0x81, + 0x90, + 0x55, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x80, + 0x54, + 0x90, + 0x50, + 0x90, + 0x56, + 0x5b, + 0x60, + 0x01, + 0x5f, + 0x80, + 0x82, + 0x82, + 0x54, + 0x61, + 0x00, + 0xd6, + 0x91, + 0x90, + 0x61, + 0x01, + 0x70, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x50, + 0x81, + 0x90, + 0x55, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x81, + 0x90, + 0x50, + 0x91, + 0x90, + 0x50, + 0x56, + 0x5b, + 0x61, + 0x00, + 0xf1, + 0x81, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x82, + 0x52, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x60, + 0x20, + 0x82, + 0x01, + 0x90, + 0x50, + 0x61, + 0x01, + 0x0a, + 0x5f, + 0x83, + 0x01, + 0x84, + 0x61, + 0x00, + 0xe8, + 0x56, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x7f, + 0x4e, + 0x48, + 0x7b, + 0x71, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x5f, + 0x52, + 0x60, + 0x11, + 0x60, + 0x04, + 0x52, + 0x60, + 0x24, + 0x5f, + 0xfd, + 0x5b, + 0x5f, + 0x61, + 0x01, + 0x47, + 0x82, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x91, + 0x50, + 0x61, + 0x01, + 0x52, + 0x83, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x82, + 0x82, + 0x01, + 0x90, + 0x50, + 0x80, + 0x82, + 0x11, + 0x15, + 0x61, + 0x01, + 0x6a, + 0x57, + 0x61, + 0x01, + 0x69, + 0x61, + 0x01, + 0x10, + 0x56, + 0x5b, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x61, + 0x01, + 0x7a, + 0x82, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x91, + 0x50, + 0x61, + 0x01, + 0x85, + 0x83, + 0x61, + 0x00, + 0xdf, + 0x56, + 0x5b, + 0x92, + 0x50, + 0x82, + 0x82, + 0x03, + 0x90, + 0x50, + 0x81, + 0x81, + 0x11, + 0x15, + 0x61, + 0x01, + 0x9d, + 0x57, + 0x61, + 0x01, + 0x9c, + 0x61, + 0x01, + 0x10, + 0x56, + 0x5b, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0xfe, + 0xa2, + 0x64, + 0x69, + 0x70, + 0x66, + 0x73, + 0x58, + 0x22, + 0x12, + 0x20, + 0x7e, + 0x79, + 0x2f, + 0xcf, + 0xf2, + 0x8a, + 0x4b, + 0xf0, + 0xba, + 0xd8, + 0x67, + 0x5c, + 0x5b, + 0xc2, + 0x28, + 0x8b, + 0x07, + 0x83, + 0x5a, + 0xeb, + 0xaa, + 0x90, + 0xb8, + 0xdc, + 0x5e, + 0x0d, + 0xf1, + 0x91, + 0x83, + 0xfb, + 0x72, + 0xcf, + 0x64, + 0x73, + 0x6f, + 0x6c, + 0x63, + 0x43, + 0x00, + 0x08, + 0x16, + 0x00, + 0x33 + ].span() +} + + +// // SPDX-License-Identifier: GPL-3.0 + +// pragma solidity >=0.8.2 <0.9.0; + +// /** +// * @title Storage +// * @dev Store & retrieve value in a variable +// * @custom:dev-run-script ./scripts/deploy_with_ethers.ts +// */ +// contract Storage { + +// uint256 number; + +// /** +// * @dev Store value in variable +// * @param num value to store +// */ +// function store(uint256 num) public { +// number = num; +// } + +// /** +// * @dev Return value +// * @return value of 'number' +// */ +// function retrieve() public view returns (uint256){ +// return number; +// } +// } +// Remix compiler: 0.8.20+commit.a1b79de6 +pub fn storage_evm_initcode() -> Span { + [ + 0x60, + 0x80, + 0x60, + 0x40, + 0x52, + 0x34, + 0x80, + 0x15, + 0x61, + 0x00, + 0x0f, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x61, + 0x01, + 0x43, + 0x80, + 0x61, + 0x00, + 0x1d, + 0x5f, + 0x39, + 0x5f, + 0xf3, + 0xfe, + 0x60, + 0x80, + 0x60, + 0x40, + 0x52, + 0x34, + 0x80, + 0x15, + 0x61, + 0x00, + 0x0f, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x60, + 0x04, + 0x36, + 0x10, + 0x61, + 0x00, + 0x34, + 0x57, + 0x5f, + 0x35, + 0x60, + 0xe0, + 0x1c, + 0x80, + 0x63, + 0x2e, + 0x64, + 0xce, + 0xc1, + 0x14, + 0x61, + 0x00, + 0x38, + 0x57, + 0x80, + 0x63, + 0x60, + 0x57, + 0x36, + 0x1d, + 0x14, + 0x61, + 0x00, + 0x56, + 0x57, + 0x5b, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x61, + 0x00, + 0x40, + 0x61, + 0x00, + 0x72, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x61, + 0x00, + 0x4d, + 0x91, + 0x90, + 0x61, + 0x00, + 0x9b, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x80, + 0x91, + 0x03, + 0x90, + 0xf3, + 0x5b, + 0x61, + 0x00, + 0x70, + 0x60, + 0x04, + 0x80, + 0x36, + 0x03, + 0x81, + 0x01, + 0x90, + 0x61, + 0x00, + 0x6b, + 0x91, + 0x90, + 0x61, + 0x00, + 0xe2, + 0x56, + 0x5b, + 0x61, + 0x00, + 0x7a, + 0x56, + 0x5b, + 0x00, + 0x5b, + 0x5f, + 0x80, + 0x54, + 0x90, + 0x50, + 0x90, + 0x56, + 0x5b, + 0x80, + 0x5f, + 0x81, + 0x90, + 0x55, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x81, + 0x90, + 0x50, + 0x91, + 0x90, + 0x50, + 0x56, + 0x5b, + 0x61, + 0x00, + 0x95, + 0x81, + 0x61, + 0x00, + 0x83, + 0x56, + 0x5b, + 0x82, + 0x52, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x60, + 0x20, + 0x82, + 0x01, + 0x90, + 0x50, + 0x61, + 0x00, + 0xae, + 0x5f, + 0x83, + 0x01, + 0x84, + 0x61, + 0x00, + 0x8c, + 0x56, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x61, + 0x00, + 0xc1, + 0x81, + 0x61, + 0x00, + 0x83, + 0x56, + 0x5b, + 0x81, + 0x14, + 0x61, + 0x00, + 0xcb, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x81, + 0x35, + 0x90, + 0x50, + 0x61, + 0x00, + 0xdc, + 0x81, + 0x61, + 0x00, + 0xb8, + 0x56, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x60, + 0x20, + 0x82, + 0x84, + 0x03, + 0x12, + 0x15, + 0x61, + 0x00, + 0xf7, + 0x57, + 0x61, + 0x00, + 0xf6, + 0x61, + 0x00, + 0xb4, + 0x56, + 0x5b, + 0x5b, + 0x5f, + 0x61, + 0x01, + 0x04, + 0x84, + 0x82, + 0x85, + 0x01, + 0x61, + 0x00, + 0xce, + 0x56, + 0x5b, + 0x91, + 0x50, + 0x50, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0xfe, + 0xa2, + 0x64, + 0x69, + 0x70, + 0x66, + 0x73, + 0x58, + 0x22, + 0x12, + 0x20, + 0xb5, + 0xc3, + 0x07, + 0x5f, + 0x2f, + 0x20, + 0x34, + 0xd0, + 0x39, + 0xa2, + 0x27, + 0xfa, + 0xc6, + 0xdd, + 0x31, + 0x4b, + 0x05, + 0x2f, + 0xfb, + 0x2b, + 0x3d, + 0xa5, + 0x2c, + 0x7b, + 0x6f, + 0x5b, + 0xc3, + 0x74, + 0xd5, + 0x28, + 0xed, + 0x36, + 0x64, + 0x73, + 0x6f, + 0x6c, + 0x63, + 0x43, + 0x00, + 0x08, + 0x14, + 0x00, + 0x33 + ].span() +} + +pub fn storage_evm_bytecode() -> Span { + [ + 0x60, + 0x80, + 0x60, + 0x40, + 0x52, + 0x34, + 0x80, + 0x15, + 0x61, + 0x00, + 0x0f, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x60, + 0x04, + 0x36, + 0x10, + 0x61, + 0x00, + 0x34, + 0x57, + 0x5f, + 0x35, + 0x60, + 0xe0, + 0x1c, + 0x80, + 0x63, + 0x2e, + 0x64, + 0xce, + 0xc1, + 0x14, + 0x61, + 0x00, + 0x38, + 0x57, + 0x80, + 0x63, + 0x60, + 0x57, + 0x36, + 0x1d, + 0x14, + 0x61, + 0x00, + 0x56, + 0x57, + 0x5b, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x61, + 0x00, + 0x40, + 0x61, + 0x00, + 0x72, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x61, + 0x00, + 0x4d, + 0x91, + 0x90, + 0x61, + 0x00, + 0x9b, + 0x56, + 0x5b, + 0x60, + 0x40, + 0x51, + 0x80, + 0x91, + 0x03, + 0x90, + 0xf3, + 0x5b, + 0x61, + 0x00, + 0x70, + 0x60, + 0x04, + 0x80, + 0x36, + 0x03, + 0x81, + 0x01, + 0x90, + 0x61, + 0x00, + 0x6b, + 0x91, + 0x90, + 0x61, + 0x00, + 0xe2, + 0x56, + 0x5b, + 0x61, + 0x00, + 0x7a, + 0x56, + 0x5b, + 0x00, + 0x5b, + 0x5f, + 0x80, + 0x54, + 0x90, + 0x50, + 0x90, + 0x56, + 0x5b, + 0x80, + 0x5f, + 0x81, + 0x90, + 0x55, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x81, + 0x90, + 0x50, + 0x91, + 0x90, + 0x50, + 0x56, + 0x5b, + 0x61, + 0x00, + 0x95, + 0x81, + 0x61, + 0x00, + 0x83, + 0x56, + 0x5b, + 0x82, + 0x52, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x60, + 0x20, + 0x82, + 0x01, + 0x90, + 0x50, + 0x61, + 0x00, + 0xae, + 0x5f, + 0x83, + 0x01, + 0x84, + 0x61, + 0x00, + 0x8c, + 0x56, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x61, + 0x00, + 0xc1, + 0x81, + 0x61, + 0x00, + 0x83, + 0x56, + 0x5b, + 0x81, + 0x14, + 0x61, + 0x00, + 0xcb, + 0x57, + 0x5f, + 0x80, + 0xfd, + 0x5b, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x81, + 0x35, + 0x90, + 0x50, + 0x61, + 0x00, + 0xdc, + 0x81, + 0x61, + 0x00, + 0xb8, + 0x56, + 0x5b, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0x5b, + 0x5f, + 0x60, + 0x20, + 0x82, + 0x84, + 0x03, + 0x12, + 0x15, + 0x61, + 0x00, + 0xf7, + 0x57, + 0x61, + 0x00, + 0xf6, + 0x61, + 0x00, + 0xb4, + 0x56, + 0x5b, + 0x5b, + 0x5f, + 0x61, + 0x01, + 0x04, + 0x84, + 0x82, + 0x85, + 0x01, + 0x61, + 0x00, + 0xce, + 0x56, + 0x5b, + 0x91, + 0x50, + 0x50, + 0x92, + 0x91, + 0x50, + 0x50, + 0x56, + 0xfe, + 0xa2, + 0x64, + 0x69, + 0x70, + 0x66, + 0x73, + 0x58, + 0x22, + 0x12, + 0x20, + 0xb5, + 0xc3, + 0x07, + 0x5f, + 0x2f, + 0x20, + 0x34, + 0xd0, + 0x39, + 0xa2, + 0x27, + 0xfa, + 0xc6, + 0xdd, + 0x31, + 0x4b, + 0x05, + 0x2f, + 0xfb, + 0x2b, + 0x3d, + 0xa5, + 0x2c, + 0x7b, + 0x6f, + 0x5b, + 0xc3, + 0x74, + 0xd5, + 0x28, + 0xed, + 0x36, + 0x64, + 0x73, + 0x6f, + 0x6c, + 0x63, + 0x43, + 0x00, + 0x08, + 0x14, + 0x00, + 0x33 + ].span() +} + + +// eip-2930 RLP encoded tx { unsigned }, calls the `inc` function of counter bytecode +// format: 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList]) +// rlp decoding: [ '0x01', '0x', '0x3b9aca00', '0x1e8480', +// '0x0000006f746865725f65766d5f61646472657373', '0x', '0x371303c0', [] ] +pub fn eip_2930_rlp_encoded_counter_inc_tx() -> Span { + [ + 1, + 235, + 132, + 75, + 75, + 82, + 84, + 128, + 132, + 59, + 154, + 202, + 0, + 131, + 30, + 132, + 128, + 148, + 0, + 0, + 0, + 111, + 116, + 104, + 101, + 114, + 95, + 101, + 118, + 109, + 95, + 97, + 100, + 100, + 114, + 101, + 115, + 115, + 128, + 132, + 55, + 19, + 3, + 192, + 192, + ].span() +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/test_utils.cairo b/cairo/kakarot-ssj/crates/contracts/src/test_utils.cairo new file mode 100644 index 000000000..e56908366 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/test_utils.cairo @@ -0,0 +1,147 @@ +use core::result::ResultTrait; +use core::starknet::syscalls::deploy_syscall; +use core::starknet::{EthAddress, ContractAddress}; +use crate::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; +use crate::kakarot_core::{ + interface::IExtendedKakarotCoreDispatcher, interface::IExtendedKakarotCoreDispatcherTrait +}; +use evm::model::{Address}; + +use evm::test_utils::{other_starknet_address, sequencer_evm_address, chain_id}; +use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; +use snforge_std::start_cheat_chain_id_global; +use snforge_std::{ + declare, DeclareResultTrait, start_cheat_caller_address, start_cheat_sequencer_address_global, + stop_cheat_caller_address, start_cheat_caller_address_global, cheat_caller_address, CheatSpan +}; +use utils::constants::BLOCK_GAS_LIMIT; +use utils::eth_transaction::legacy::TxLegacy; + + +pub mod constants { + use core::starknet::{EthAddress, contract_address_const, ContractAddress}; + pub fn ZERO() -> ContractAddress { + contract_address_const::<0>() + } + + pub fn OWNER() -> ContractAddress { + contract_address_const::<0xabde1>() + } + + pub fn OTHER() -> ContractAddress { + contract_address_const::<0xe1145>() + } + + pub fn EVM_ADDRESS() -> EthAddress { + 0xc0ffee.try_into().unwrap() + } + + pub fn ETH_BANK() -> ContractAddress { + contract_address_const::<0x777>() + } +} + +pub fn deploy_native_token() -> IERC20CamelDispatcher { + let calldata: Array = array![ + 'STARKNET_ETH', 'ETH', 0x00, 0xfffffffffffffffffffffffffff, constants::ETH_BANK().into() + ]; + let class = declare("ERC20").unwrap().contract_class().class_hash; + let maybe_address = deploy_syscall(*class, 0, calldata.span(), false); + match maybe_address { + Result::Ok((contract_address, _)) => { IERC20CamelDispatcher { contract_address } }, + Result::Err(err) => panic(err) + } +} + +pub fn deploy_kakarot_core( + native_token: ContractAddress, mut eoas: Span +) -> IExtendedKakarotCoreDispatcher { + let account_contract_class_hash = declare("AccountContract") + .unwrap() + .contract_class() + .class_hash; + let uninitialized_account_class_hash = declare("UninitializedAccount") + .unwrap() + .contract_class() + .class_hash; + let kakarot_core_class_hash = declare("KakarotCore").unwrap().contract_class().class_hash; + let mut calldata: Array = array![ + other_starknet_address().into(), + native_token.into(), + (*account_contract_class_hash).into(), + (*uninitialized_account_class_hash).into(), + 'coinbase', + BLOCK_GAS_LIMIT.into(), + ]; + + Serde::serialize(@eoas, ref calldata); + + let maybe_address = deploy_syscall( + (*kakarot_core_class_hash).into(), 0, calldata.span(), false + ); + + let kakarot_core = match maybe_address { + Result::Ok(( + contract_address, _ + )) => { IExtendedKakarotCoreDispatcher { contract_address } }, + Result::Err(err) => panic(err) + }; + cheat_caller_address( + kakarot_core.contract_address, other_starknet_address(), CheatSpan::TargetCalls(1) + ); + kakarot_core.set_base_fee(1000); + + kakarot_core +} + +pub fn deploy_contract_account( + kakarot_core: IExtendedKakarotCoreDispatcher, evm_address: EthAddress, bytecode: Span +) -> Address { + let eoa = deploy_eoa(kakarot_core, evm_address); + let starknet_address = eoa.contract_address; + start_cheat_caller_address(starknet_address, kakarot_core.contract_address); + IAccountDispatcher { contract_address: starknet_address }.set_nonce(1); + IAccountDispatcher { contract_address: starknet_address }.write_bytecode(bytecode); + stop_cheat_caller_address(starknet_address); + Address { evm: evm_address, starknet: starknet_address } +} + +pub fn deploy_eoa( + kakarot_core: IExtendedKakarotCoreDispatcher, evm_address: EthAddress +) -> IAccountDispatcher { + let starknet_address = kakarot_core.deploy_externally_owned_account(evm_address); + IAccountDispatcher { contract_address: starknet_address } +} + +pub fn call_transaction( + chain_id: u64, destination: Option, input: Span +) -> TxLegacy { + TxLegacy { + chain_id: Option::Some(chain_id), + nonce: 0, + gas_price: 0, + gas_limit: 500000000, + to: destination.into(), + value: 0, + input + } +} + +pub fn fund_account_with_native_token( + contract_address: ContractAddress, native_token: IERC20CamelDispatcher, amount: u256, +) { + start_cheat_caller_address(native_token.contract_address, constants::ETH_BANK()); + native_token.transfer(contract_address, amount); + stop_cheat_caller_address(native_token.contract_address); +} + +pub fn setup_contracts_for_testing() -> (IERC20CamelDispatcher, IExtendedKakarotCoreDispatcher) { + let sequencer: EthAddress = sequencer_evm_address(); + let native_token = deploy_native_token(); + let kakarot_core = deploy_kakarot_core(native_token.contract_address, [sequencer].span()); + + let sequencer_sn_address = kakarot_core.address_registry(sequencer); + start_cheat_sequencer_address_global(sequencer_sn_address); + start_cheat_chain_id_global(chain_id().into()); + return (native_token, kakarot_core); +} diff --git a/cairo/kakarot-ssj/crates/contracts/src/uninitialized_account.cairo b/cairo/kakarot-ssj/crates/contracts/src/uninitialized_account.cairo new file mode 100644 index 000000000..46c4ea9e0 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/src/uninitialized_account.cairo @@ -0,0 +1,66 @@ +//! The generic account that is deployed by Kakarot Core before being "specialized" into an +//! Externally Owned Account or a Contract Account This aims at having only one class hash for all +//! the contracts deployed by Kakarot, thus enforcing a unique and consistent address mapping Eth +//! Address <=> Starknet Address + +use core::starknet::ClassHash; +use core::starknet::{ContractAddress, EthAddress}; + +#[starknet::interface] +trait IAccount { + fn initialize( + ref self: TContractState, + kakarot_address: ContractAddress, + evm_address: EthAddress, + implementation_class: ClassHash + ); +} + + +#[starknet::contract] +pub mod UninitializedAccount { + use core::starknet::SyscallResultTrait; + use core::starknet::syscalls::{library_call_syscall, replace_class_syscall}; + use core::starknet::{ContractAddress, get_caller_address}; + use crate::components::ownable::ownable_component::InternalTrait; + use crate::components::ownable::ownable_component; + use crate::kakarot_core::interface::{IKakarotCoreDispatcher, IKakarotCoreDispatcherTrait}; + + // Add ownable component + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::Ownable; + impl OwnableInternal = ownable_component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: ownable_component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + OwnableEvent: ownable_component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, mut calldata: Array) { + let owner_address: ContractAddress = get_caller_address(); + self.ownable.initializer(owner_address); + let implementation_class = IKakarotCoreDispatcher { contract_address: owner_address } + .get_account_contract_class_hash(); + //TODO: Difference from KakarotZero in that the account contract takes the class + //implementation to write it in storage, + // as it is not a transparent proxy in Cairo1 + calldata.append(implementation_class.into()); + library_call_syscall( + class_hash: implementation_class, + function_selector: selector!("initialize"), + calldata: calldata.span() + ) + .unwrap_syscall(); + + replace_class_syscall(implementation_class).unwrap_syscall(); + } +} diff --git a/cairo/kakarot-ssj/crates/contracts/tests/lib.cairo b/cairo/kakarot-ssj/crates/contracts/tests/lib.cairo new file mode 100644 index 000000000..df3d540ee --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/tests/lib.cairo @@ -0,0 +1,8 @@ +mod test_account_contract; +mod test_cairo1_helpers; + +mod test_execution_from_outside; + +mod test_kakarot_core; + +mod test_ownable; diff --git a/cairo/kakarot-ssj/crates/contracts/tests/test_account_contract.cairo b/cairo/kakarot-ssj/crates/contracts/tests/test_account_contract.cairo new file mode 100644 index 000000000..430a5501c --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/tests/test_account_contract.cairo @@ -0,0 +1,148 @@ +use contracts::errors::KAKAROT_REENTRANCY; +use contracts::test_data::counter_evm_bytecode; +use contracts::test_utils::{ + setup_contracts_for_testing, deploy_contract_account, fund_account_with_native_token, deploy_eoa +}; +use contracts::{IAccountDispatcher, IAccountDispatcherTrait}; +use core::starknet::EthAddress; +use core::starknet::account::{Call}; +use evm::test_utils::{ca_address, eoa_address}; +use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait; +use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address}; + +#[test] +fn test_ca_deploy() { + let (_, kakarot_core) = setup_contracts_for_testing(); + let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span()); + let contract_account = IAccountDispatcher { contract_address: ca_address.starknet }; + + let initial_bytecode = contract_account.bytecode(); + assert(initial_bytecode.is_empty(), 'bytecode should be empty'); + assert(contract_account.get_evm_address() == ca_address.evm, 'wrong ca evm address'); + assert(contract_account.get_nonce() == 1, 'wrong nonce'); +} + +#[test] +fn test_ca_bytecode() { + let (_, kakarot_core) = setup_contracts_for_testing(); + let bytecode = counter_evm_bytecode(); + let ca_address = deploy_contract_account(kakarot_core, ca_address(), bytecode); + let contract_account = IAccountDispatcher { contract_address: ca_address.starknet }; + + let contract_bytecode = contract_account.bytecode(); + assert(contract_bytecode == bytecode, 'wrong contract bytecode'); +} + + +#[test] +fn test_ca_get_nonce() { + let (_, kakarot_core) = setup_contracts_for_testing(); + let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span()); + let contract_account = IAccountDispatcher { contract_address: ca_address.starknet }; + + let initial_nonce = contract_account.get_nonce(); + assert(initial_nonce == 1, 'nonce should be 1'); + + let expected_nonce = 100; + start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address); + contract_account.set_nonce(expected_nonce); + stop_cheat_caller_address(ca_address.starknet); + + let nonce = contract_account.get_nonce(); + + assert(nonce == expected_nonce, 'wrong contract nonce'); +} + +#[test] +fn test_get_evm_address() { + let expected_address: EthAddress = eoa_address(); + let (_, kakarot_core) = setup_contracts_for_testing(); + + let eoa_contract = deploy_eoa(kakarot_core, eoa_address()); + + assert(eoa_contract.get_evm_address() == expected_address, 'wrong evm_address'); +} + + +#[test] +fn test_ca_storage() { + let (_, kakarot_core) = setup_contracts_for_testing(); + let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span()); + let contract_account = IAccountDispatcher { contract_address: ca_address.starknet }; + + let storage_slot = 0x555; + + let initial_storage = contract_account.storage(storage_slot); + assert(initial_storage == 0, 'value should be 0'); + + let expected_storage = 0x444; + start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address); + contract_account.write_storage(storage_slot, expected_storage); + stop_cheat_caller_address(ca_address.starknet); + + let storage = contract_account.storage(storage_slot); + + assert(storage == expected_storage, 'wrong contract storage'); +} + +#[test] +fn test_ca_external_starknet_call_native_token() { + let (native_token, kakarot_core) = setup_contracts_for_testing(); + let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span()); + let contract_account = IAccountDispatcher { contract_address: ca_address.starknet }; + fund_account_with_native_token(ca_address.starknet, native_token, 0x1); + + let call = Call { + to: native_token.contract_address, + selector: selector!("balanceOf"), + calldata: array![ca_address.starknet.into()].span(), + }; + start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address); + let (success, data) = contract_account.execute_starknet_call(call); + stop_cheat_caller_address(ca_address.starknet); + + assert(success, 'execute_starknet_call failed'); + assert(data.len() == 2, 'wrong return data length'); + let balance = native_token.balanceOf(ca_address.starknet); + assert((*data[0], *data[1]) == (balance.low.into(), balance.high.into()), 'wrong return data'); +} + +#[test] +fn test_ca_external_starknet_call_kakarot_get_starknet_address() { + let (_, kakarot_core) = setup_contracts_for_testing(); + let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span()); + let contract_account = IAccountDispatcher { contract_address: ca_address.starknet }; + + let call = Call { + to: kakarot_core.contract_address, selector: selector!("get_starknet_address"), calldata: [ + ca_address.evm.into() + ].span(), + }; + start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address); + let (success, data) = contract_account.execute_starknet_call(call); + stop_cheat_caller_address(ca_address.starknet); + + assert(success, 'execute_starknet_call failed'); + assert(data.len() == 1, 'wrong return data length'); + assert(*data[0] == ca_address.starknet.try_into().unwrap(), 'wrong return data'); +} + +#[test] +fn test_ca_external_starknet_call_cannot_call_kakarot_other_selector() { + let (_, kakarot_core) = setup_contracts_for_testing(); + let ca_address = deploy_contract_account(kakarot_core, ca_address(), [].span()); + let contract_account = IAccountDispatcher { contract_address: ca_address.starknet }; + + let call = Call { + to: kakarot_core.contract_address, + selector: selector!("get_native_token"), + calldata: [].span(), + }; + start_cheat_caller_address(ca_address.starknet, kakarot_core.contract_address); + let (success, data) = contract_account.execute_starknet_call(call); + stop_cheat_caller_address(ca_address.starknet); + + assert(!success, 'execute_starknet_call failed'); + assert(data.len() == 19, 'wrong return data length'); + assert(data == KAKAROT_REENTRANCY.span(), 'wrong return data'); +} diff --git a/cairo/kakarot-ssj/crates/contracts/tests/test_cairo1_helpers.cairo b/cairo/kakarot-ssj/crates/contracts/tests/test_cairo1_helpers.cairo new file mode 100644 index 000000000..ace7677d7 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/tests/test_cairo1_helpers.cairo @@ -0,0 +1,20 @@ +use contracts::cairo1_helpers::Cairo1Helpers; +use utils::traits::integer::BytesUsedTrait; + +#[test] +fn test_keccak() { + // "Hello world!" + // where: + // 8031924123371070792 == int.from_bytes(b'Hello wo', 'little') + // 560229490 == int.from_bytes(b'rld!', 'little') + let input = array![8031924123371070792]; + let last_input_word: u64 = 560229490; + let last_input_num_bytes = last_input_word.bytes_used(); + let state = Cairo1Helpers::contract_state_for_testing(); + + let res = Cairo1Helpers::Helpers::keccak( + @state, input, last_input_word, last_input_num_bytes.into() + ); + + assert_eq!(res, 0xecd0e108a98e192af1d2c25055f4e3bed784b5c877204e73219a5203251feaab); +} diff --git a/cairo/kakarot-ssj/crates/contracts/tests/test_execution_from_outside.cairo b/cairo/kakarot-ssj/crates/contracts/tests/test_execution_from_outside.cairo new file mode 100644 index 000000000..568565968 --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/tests/test_execution_from_outside.cairo @@ -0,0 +1,514 @@ +use contracts::account_contract::{ + AccountContract, IAccountDispatcher, IAccountDispatcherTrait, OutsideExecution +}; +use contracts::kakarot_core::interface::{ + IExtendedKakarotCoreDispatcher, IExtendedKakarotCoreDispatcherTrait +}; +use contracts::test_data::{counter_evm_bytecode, eip_2930_rlp_encoded_counter_inc_tx,}; +use contracts::test_utils::{ + setup_contracts_for_testing, deploy_contract_account, fund_account_with_native_token, + call_transaction +}; +use core::num::traits::Bounded; +use core::starknet::account::Call; +use core::starknet::secp256_trait::Signature; +use core::starknet::{ContractAddress, contract_address_const, EthAddress, Event}; +use evm::test_utils::chain_id; +use evm::test_utils::other_evm_address; +use openzeppelin::token::erc20::interface::IERC20CamelDispatcher; +use snforge_std::{ + start_cheat_caller_address, stop_cheat_caller_address, start_cheat_transaction_hash, spy_events, + EventSpyTrait, CheatSpan, cheat_caller_address, stop_cheat_block_timestamp, + start_cheat_block_timestamp, start_cheat_chain_id_global, stop_cheat_chain_id_global, + stop_cheat_caller_address_global +}; + +use snforge_utils::snforge_utils::EventsFilterBuilderTrait; +use utils::eth_transaction::transaction::Transaction; +use utils::eth_transaction::tx_type::TxType; +use utils::helpers::u256_to_bytes_array; +use utils::serialization::{serialize_bytes, serialize_transaction_signature}; +use utils::test_data::{legacy_rlp_encoded_tx, eip_2930_encoded_tx, eip_1559_encoded_tx}; + +fn transaction_signer() -> EthAddress { + 0x6Bd85F39321B00c6d603474C5B2fddEB9c92A466.try_into().unwrap() +} + +const PLACEHOLDER_SIGNATURE: [felt252; 5] = [1, 2, 3, 4, 0]; + +const SNIP9_CALLER: felt252 = 0xaA36F24f65b5F0f2c642323f3d089A3F0f2845Bf; + +#[derive(Drop)] +struct CallBuilder { + call: Call +} + +#[generate_trait] +impl CallBuilderImpl of CallBuilderTrait { + fn new(kakarot_core: ContractAddress) -> CallBuilder { + CallBuilder { + call: Call { + to: kakarot_core, + selector: selector!("eth_send_transaction"), + calldata: serialize_bytes(eip_2930_encoded_tx()).span() + } + } + } + + fn with_to(mut self: CallBuilder, to: ContractAddress) -> CallBuilder { + self.call.to = to; + self + } + + fn with_selector(mut self: CallBuilder, selector: felt252) -> CallBuilder { + self.call.selector = selector; + self + } + + fn with_calldata(mut self: CallBuilder, calldata: Span) -> CallBuilder { + self.call.calldata = serialize_bytes(calldata).span(); + self + } + + fn build(mut self: CallBuilder) -> Call { + return self.call; + } +} + +#[derive(Drop)] +struct OutsideExecutionBuilder { + outside_execution: OutsideExecution +} + +#[generate_trait] +impl OutsideExecutionBuilderImpl of OutsideExecutionBuilderTrait { + fn new(kakarot_core: ContractAddress) -> OutsideExecutionBuilder { + OutsideExecutionBuilder { + outside_execution: OutsideExecution { + caller: 'ANY_CALLER'.try_into().unwrap(), + nonce: 0, + execute_after: 998, + execute_before: 1000, + calls: [ + CallBuilderTrait::new(kakarot_core).build(), + ].span() + } + } + } + + fn with_caller( + mut self: OutsideExecutionBuilder, caller: ContractAddress + ) -> OutsideExecutionBuilder { + self.outside_execution.caller = caller; + self + } + + fn with_nonce(mut self: OutsideExecutionBuilder, nonce: u64) -> OutsideExecutionBuilder { + self.outside_execution.nonce = nonce; + self + } + + fn with_execute_after( + mut self: OutsideExecutionBuilder, execute_after: u64 + ) -> OutsideExecutionBuilder { + self.outside_execution.execute_after = execute_after; + self + } + + fn with_execute_before( + mut self: OutsideExecutionBuilder, execute_before: u64 + ) -> OutsideExecutionBuilder { + self.outside_execution.execute_before = execute_before; + self + } + + fn with_calls(mut self: OutsideExecutionBuilder, calls: Span) -> OutsideExecutionBuilder { + self.outside_execution.calls = calls; + self + } + + fn build(mut self: OutsideExecutionBuilder) -> OutsideExecution { + return self.outside_execution; + } +} + +fn set_up() -> (IExtendedKakarotCoreDispatcher, IAccountDispatcher, IERC20CamelDispatcher) { + let (native_token, kakarot_core) = setup_contracts_for_testing(); + // When we deploy the EOA, we use get_caller_address to get the address of the KakarotCore + // contract and set the caller address to that. + // Therefore, we need to stop the global caller address cheat so that the EOA is deployed + // by the real KakarotCore contract and not the one impersonated by the cheat + stop_cheat_caller_address_global(); + let eoa = IAccountDispatcher { + contract_address: kakarot_core.deploy_externally_owned_account(transaction_signer()) + }; + start_cheat_block_timestamp(eoa.contract_address, 999); + start_cheat_chain_id_global(chain_id().into()); + + (kakarot_core, eoa, native_token) +} + +fn tear_down(contract_account: IAccountDispatcher) { + stop_cheat_chain_id_global(); + stop_cheat_block_timestamp(contract_account.contract_address); +} + +#[test] +#[should_panic(expected: 'SNIP9: Invalid caller')] +fn test_execute_from_outside_invalid_caller() { + let (kakarot_core, contract_account, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_caller(contract_address_const::<0xb0b>()) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + let _ = contract_account.execute_from_outside(outside_execution, signature); + + tear_down(contract_account); +} + +#[test] +#[should_panic(expected: 'SNIP9: Too early call')] +fn test_execute_from_outside_too_early_call() { + let (kakarot_core, contract_account, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_execute_after(999) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + let _ = contract_account.execute_from_outside(outside_execution, signature); + + tear_down(contract_account); +} + +#[test] +#[should_panic(expected: 'SNIP9: Too late call')] +fn test_execute_from_outside_too_late_call() { + let (kakarot_core, contract_account, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_execute_before(999) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + let _ = contract_account.execute_from_outside(outside_execution, signature); + + tear_down(contract_account); +} + +#[test] +#[should_panic(expected: 'EOA: Invalid signature length')] +fn test_execute_from_outside_inPLACEHOLDER_SIGNATURE_length() { + let (kakarot_core, contract_account, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .build(); + + let _ = contract_account.execute_from_outside(outside_execution, [].span()); + + tear_down(contract_account); +} + +#[test] +#[should_panic(expected: 'KKRT: Multicall not supported')] +fn test_execute_from_outside_multicall_not_supported() { + let (kakarot_core, contract_account, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_calls( + [ + CallBuilderTrait::new(kakarot_core.contract_address).build(), + CallBuilderTrait::new(kakarot_core.contract_address).build(), + ].span() + ) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + let _ = contract_account.execute_from_outside(outside_execution, signature); + + tear_down(contract_account); +} + +#[test] +#[should_panic(expected: 'EOA: invalid signature')] +fn test_execute_from_outside_invalid_signature() { + let (kakarot_core, contract_account, _) = set_up(); + + let caller = contract_address_const::(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_caller(caller) + .build(); + let signature: Span = [1, 2, 3, 4, (chain_id() * 2 + 40).into()].span(); + + start_cheat_caller_address(contract_account.contract_address, caller); + let _ = contract_account.execute_from_outside(outside_execution, signature); + + tear_down(contract_account); +} + + +#[test] +#[should_panic(expected: 'KKRT: Multicall not supported')] +fn test_execute_from_outside_should_fail_with_zero_calls() { + let (kakarot_core, contract_account, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_calls([].span()) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + let _ = contract_account.execute_from_outside(outside_execution, signature); + + tear_down(contract_account); +} + +#[test] +#[should_panic(expected: 'EOA: cannot have code')] +fn test_execute_from_outside_should_fail_account_with_code() { + let (kakarot_core, _, _) = set_up(); + + let contract_address = deploy_contract_account( + kakarot_core, other_evm_address(), counter_evm_bytecode() + ) + .starknet; + let contract_account = IAccountDispatcher { contract_address }; + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + start_cheat_block_timestamp(contract_account.contract_address, 999); + let _ = contract_account.execute_from_outside(outside_execution, signature); + + tear_down(contract_account); +} + + +#[test] +#[should_panic(expected: 'KKRT: Multicall not supported')] +fn test_execute_from_outside_should_fail_with_multi_calls() { + let (kakarot_core, eoa, _) = set_up(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_calls( + [ + CallBuilderTrait::new(kakarot_core.contract_address) + .with_calldata(legacy_rlp_encoded_tx()) + .build() + ; 2].span() + ) + .build(); + let signature = PLACEHOLDER_SIGNATURE.span(); + + let _ = eoa.execute_from_outside(outside_execution, signature); + + tear_down(eoa); +} + + +#[test] +fn test_execute_from_outside_legacy_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + + let caller = contract_address_const::(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_caller(caller) + .with_calls( + [ + CallBuilderTrait::new(kakarot_core.contract_address) + .with_calldata(legacy_rlp_encoded_tx()) + .build() + ].span() + ) + .build(); + + // to reproduce locally: + // run: cp .env.example .env + // bun install & bun run scripts/compute_rlp_encoding.ts + + let signature = serialize_transaction_signature( + Signature { + r: 0xf2805d01dd4fa240c79039c85a77554fc186cc73c2025d7f8c02bc8fe1a982b5, + s: 0x27ff351275563c1a29ab9eaeec4a3b63fbc4035d6da6b8b6af52c7993b5869ec, + y_parity: true + }, + TxType::Legacy, + chain_id() + ) + .span(); + + cheat_caller_address(eoa.contract_address, caller, CheatSpan::TargetCalls(1)); + stop_cheat_caller_address(kakarot_core.contract_address); + let data = eoa.execute_from_outside(outside_execution, signature); + + assert_eq!(data.len(), 1); + assert_eq!(*data[0], [].span()); + + stop_cheat_caller_address(eoa.contract_address); + tear_down(eoa); +} + +#[test] +fn test_execute_from_outside_eip2930_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + let caller = contract_address_const::(); + + // Signature for the default eip2930 tx + let signature = Signature { + r: 0x5c4ae1ed01c8df4277f02aa3443f8183ed44627217fd7f27badaed8795906e78, + s: 0x4d2af576441428d47c174ffddc6e70b980527a57795b3c87a71878f97ecef274, + y_parity: true + }; + + // Defaults with an eip2930 tx + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_caller(caller) + .build(); + let signature = serialize_transaction_signature(signature, TxType::Eip2930, chain_id()).span(); + + cheat_caller_address(eoa.contract_address, caller, CheatSpan::TargetCalls(1)); + stop_cheat_caller_address(kakarot_core.contract_address); + let data = eoa.execute_from_outside(outside_execution, signature); + + assert_eq!(data.len(), 1); + assert_eq!(*data[0], [].span()); + + stop_cheat_caller_address(eoa.contract_address); + tear_down(eoa); +} + + +#[test] +fn test_execute_from_outside_eip1559_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + + let caller = contract_address_const::(); + + let outside_execution = OutsideExecutionBuilderTrait::new(kakarot_core.contract_address) + .with_caller(caller) + .with_calls( + [ + CallBuilderTrait::new(kakarot_core.contract_address) + .with_calldata(eip_1559_encoded_tx()) + .build() + ].span() + ) + .build(); + + // to reproduce locally: + // run: cp .env.example .env + // bun install & bun run scripts/compute_rlp_encoding.ts + let signature = Signature { + r: 0xb2563dbafa29dd6f126f0e6581b772d3f07063e2f07fb7bdf73aad34a04c4283, + s: 0x73df539e40359b81b8f260ed04431de098fc149bc5e27120e6711acabaecd067, + y_parity: true + }; + let signature = serialize_transaction_signature(signature, TxType::Eip1559, chain_id()).span(); + + // Stop all cheats and only mock the EFO caller. + stop_cheat_caller_address_global(); + cheat_caller_address(eoa.contract_address, caller, CheatSpan::TargetCalls(1)); + let data = eoa.execute_from_outside(outside_execution, signature); + + assert_eq!(data.len(), 1); + assert_eq!(*data[0], [].span()); + + tear_down(eoa); +} + +#[test] +fn test_execute_from_outside_eip_2930_counter_inc_tx() { + let (kakarot_core, eoa, native_token) = set_up(); + fund_account_with_native_token(eoa.contract_address, native_token, Bounded::::MAX.into()); + + let kakarot_address = kakarot_core.contract_address; + + deploy_contract_account(kakarot_core, other_evm_address(), counter_evm_bytecode()); + + start_cheat_caller_address(kakarot_address, eoa.contract_address); + + // Then + // selector: function get() + let data_get_tx = [0x6d, 0x4c, 0xe6, 0x3c].span(); + + // check counter value is 0 before doing inc + let tx = call_transaction(chain_id(), Option::Some(other_evm_address()), data_get_tx); + + let (_, return_data, _) = kakarot_core + .eth_call(origin: transaction_signer(), tx: Transaction::Legacy(tx),); + + assert_eq!(return_data, u256_to_bytes_array(0).span()); + + // perform inc on the counter + let call = Call { + to: kakarot_address, + selector: selector!("eth_send_transaction"), + calldata: serialize_bytes(eip_2930_rlp_encoded_counter_inc_tx()).span() + }; + + start_cheat_transaction_hash(eoa.contract_address, selector!("transaction_hash")); + start_cheat_block_timestamp(eoa.contract_address, 100); + cheat_caller_address( + eoa.contract_address, contract_address_const::<0>(), CheatSpan::TargetCalls(1) + ); + let mut spy = spy_events(); + let outside_execution = OutsideExecution { + caller: contract_address_const::<'ANY_CALLER'>(), + nonce: 0, + execute_after: 0, + execute_before: 10000000, + calls: array![call].span() + }; + let signature = Signature { + r: 0x8cd55583b5da62b3fd23586bf4f1ffd496046b9d248a7983ec41bd6fb673f379, + s: 0x09432a74ec3720a226ac040ce828f92e22350c4d8f7b188693cad035e99372ed, + y_parity: true + }; + let signature = serialize_transaction_signature(signature, TxType::Eip2930, chain_id()).span(); + stop_cheat_caller_address(kakarot_core.contract_address); + let result = eoa.execute_from_outside(outside_execution, signature); + assert_eq!(result.len(), 1); + + let expected_event = AccountContract::Event::transaction_executed( + AccountContract::TransactionExecuted { + response: *result.span()[0], success: true, gas_used: 0 + } + ); + let mut keys = array![]; + let mut data = array![]; + expected_event.append_keys_and_data(ref keys, ref data); + let mut contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(eoa.contract_address) + .with_keys(keys.span()) + .build(); + + let mut received_keys = contract_events.events[0].keys.span(); + let mut received_data = contract_events.events[0].data.span(); + let deserialized_received: AccountContract::Event = Event::deserialize( + ref received_keys, ref received_data + ) + .unwrap(); + if let AccountContract::Event::transaction_executed(transaction_executed) = + deserialized_received { + let expected_response = *result.span()[0]; + let expected_success = true; + let not_expected_gas_used = 0; + assert_eq!(transaction_executed.response, expected_response); + assert_eq!(transaction_executed.success, expected_success); + assert_ne!(transaction_executed.gas_used, not_expected_gas_used); + } else { + panic!("Expected transaction_executed event"); + } + // check counter value has increased + let tx = call_transaction(chain_id(), Option::Some(other_evm_address()), data_get_tx); + let (_, return_data, _) = kakarot_core + .eth_call(origin: transaction_signer(), tx: Transaction::Legacy(tx),); + assert_eq!(return_data, u256_to_bytes_array(1).span()); +} diff --git a/cairo/kakarot-ssj/crates/contracts/tests/test_kakarot_core.cairo b/cairo/kakarot-ssj/crates/contracts/tests/test_kakarot_core.cairo new file mode 100644 index 000000000..29b2a32bf --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/tests/test_kakarot_core.cairo @@ -0,0 +1,429 @@ +use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; +use contracts::kakarot_core::interface::IExtendedKakarotCoreDispatcherTrait; +use contracts::kakarot_core::{KakarotCore}; +use contracts::test_contracts::test_upgradeable::{ + IMockContractUpgradeableDispatcher, IMockContractUpgradeableDispatcherTrait +}; +use contracts::test_data::{deploy_counter_calldata, counter_evm_bytecode}; +use contracts::{test_utils as contract_utils,}; +use core::num::traits::Zero; +use core::ops::SnapshotDeref; +use core::option::OptionTrait; +use core::starknet::storage::StoragePathEntry; +use core::starknet::{contract_address_const, ContractAddress, EthAddress, ClassHash}; + + +use core::traits::TryInto; +use evm::test_utils::chain_id; +use evm::test_utils; +use snforge_std::{ + declare, DeclareResultTrait, start_cheat_caller_address, spy_events, EventSpyTrait, + cheat_caller_address, CheatSpan, store, load +}; +use snforge_utils::snforge_utils::{EventsFilterBuilderTrait, ContractEventsTrait}; +use starknet::storage::StorageTrait; +use utils::eth_transaction::legacy::TxLegacy; +use utils::eth_transaction::transaction::Transaction; +use utils::helpers::{u256_to_bytes_array}; +use utils::traits::eth_address::EthAddressExTrait; + +#[test] +fn test_kakarot_core_owner() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + assert(kakarot_core.owner() == test_utils::other_starknet_address(), 'wrong owner') +} + +#[test] +fn test_kakarot_core_transfer_ownership() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + assert(kakarot_core.owner() == test_utils::other_starknet_address(), 'wrong owner'); + start_cheat_caller_address(kakarot_core.contract_address, test_utils::other_starknet_address()); + kakarot_core.transfer_ownership(test_utils::starknet_address()); + assert(kakarot_core.owner() == test_utils::starknet_address(), 'wrong owner') +} + +#[test] +fn test_kakarot_core_renounce_ownership() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + assert(kakarot_core.owner() == test_utils::other_starknet_address(), 'wrong owner'); + start_cheat_caller_address(kakarot_core.contract_address, test_utils::other_starknet_address()); + kakarot_core.renounce_ownership(); + assert(kakarot_core.owner() == contract_address_const::<0x00>(), 'wrong owner') +} + +#[test] +fn test_kakarot_core_chain_id() { + contract_utils::setup_contracts_for_testing(); + + assert(chain_id() == test_utils::chain_id(), 'wrong chain id'); +} + +#[test] +fn test_kakarot_core_set_native_token() { + let (native_token, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + assert(kakarot_core.get_native_token() == native_token.contract_address, 'wrong native_token'); + + start_cheat_caller_address(kakarot_core.contract_address, test_utils::other_starknet_address()); + kakarot_core.set_native_token(contract_address_const::<0xdead>()); + assert( + kakarot_core.get_native_token() == contract_address_const::<0xdead>(), + 'wrong new native_token' + ); +} + +#[test] +fn test_kakarot_core_deploy_eoa() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + let mut spy = spy_events(); + let eoa_starknet_address = kakarot_core + .deploy_externally_owned_account(test_utils::evm_address()); + + let expected = KakarotCore::Event::AccountDeployed( + KakarotCore::AccountDeployed { + evm_address: test_utils::evm_address(), starknet_address: eoa_starknet_address + } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(kakarot_core.contract_address) + .build(); + contract_events.assert_emitted(@expected); +} + +#[test] +fn test_kakarot_core_eoa_mapping() { + // Given + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + assert( + kakarot_core.address_registry(test_utils::evm_address()).is_zero(), + 'should be uninitialized' + ); + + let expected_eoa_starknet_address = kakarot_core + .deploy_externally_owned_account(test_utils::evm_address()); + + // When + let address = kakarot_core.address_registry(test_utils::evm_address()); + + // Then + assert_eq!(address, expected_eoa_starknet_address); + + let another_sn_address: ContractAddress = 0xbeef.try_into().unwrap(); + + // Set the address registry to the another_sn_address + let mut kakarot_state = KakarotCore::unsafe_new_contract_state(); + let map_entry_address = kakarot_state + .snapshot_deref() + .storage() + .Kakarot_evm_to_starknet_address + .entry(test_utils::evm_address()) + .deref() + .__storage_pointer_address__; + store( + kakarot_core.contract_address, map_entry_address.into(), [another_sn_address.into()].span() + ); + + let address = kakarot_core.address_registry(test_utils::evm_address()); + assert_eq!(address, another_sn_address) +} + +#[test] +fn test_kakarot_core_upgrade_contract() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + let class_hash: ClassHash = (*declare("MockContractUpgradeableV1") + .unwrap() + .contract_class() + .class_hash); + + start_cheat_caller_address(kakarot_core.contract_address, test_utils::other_starknet_address()); + kakarot_core.upgrade(class_hash); + + let version = IMockContractUpgradeableDispatcher { + contract_address: kakarot_core.contract_address + } + .version(); + assert(version == 1, 'version is not 1'); +} + +#[test] +fn test_kakarot_core_get_starknet_address_registered() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + let mut kakarot_state = KakarotCore::unsafe_new_contract_state(); + + let registered_evm_address = test_utils::evm_address(); + let registered_starknet_address = test_utils::starknet_address(); + let registered_map_entry_address = kakarot_state + .snapshot_deref() + .storage() + .Kakarot_evm_to_starknet_address + .entry(registered_evm_address) + .deref() + .__storage_pointer_address__; + // store the registered address in the mapping + store( + kakarot_core.contract_address, + registered_map_entry_address.into(), + [registered_starknet_address.into()].span() + ); + // when the address is registered in the mapping, it's returned + assert_eq!( + kakarot_core.get_starknet_address(registered_evm_address), registered_starknet_address + ); +} + +#[test] +fn test_kakarot_core_get_starknet_address_unregistered() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + let mut kakarot_state = KakarotCore::unsafe_new_contract_state(); + + let unregistered_evm_address = test_utils::other_evm_address(); + let unregistered_map_entry_address = kakarot_state + .snapshot_deref() + .storage() + .Kakarot_evm_to_starknet_address + .entry(unregistered_evm_address) + .deref() + .__storage_pointer_address__; + let map_data = load(kakarot_core.contract_address, unregistered_map_entry_address.into(), 1); + // when the map entry is empty + assert_eq!(*map_data[0], 0); + // then an unregistered address should return the same as compute_starknet_address + let computed_address = utils::helpers::compute_starknet_address( + kakarot_core.contract_address, + unregistered_evm_address, + kakarot_core.uninitialized_account_class_hash() + ); + assert_eq!(kakarot_core.get_starknet_address(unregistered_evm_address), computed_address); +} + +//TODO Needs to be restored by giving the RLP encoding of the transaction to the test +// More generally, this should _probably_ be an e2e test anyway +#[test] +#[ignore] +fn test_eth_send_transaction_non_deploy_tx() { + // Given + let (native_token, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + let evm_address = test_utils::evm_address(); + let eoa = kakarot_core.deploy_externally_owned_account(evm_address); + contract_utils::fund_account_with_native_token( + eoa, native_token, 0xfffffffffffffffffffffffffff + ); + + let counter_address = 'counter_contract'.try_into().unwrap(); + contract_utils::deploy_contract_account(kakarot_core, counter_address, counter_evm_bytecode()); + + let gas_limit = test_utils::tx_gas_limit(); + let gas_price = test_utils::gas_price(); + let value = 0; + + // Then + // selector: function get() + let data_get_tx = [0x6d, 0x4c, 0xe6, 0x3c].span(); + + // check counter value is 0 before doing inc + let tx = contract_utils::call_transaction( + chain_id(), Option::Some(counter_address), data_get_tx + ); + let (_, return_data, _) = kakarot_core + .eth_call(origin: evm_address, tx: Transaction::Legacy(tx)); + + assert_eq!(return_data, u256_to_bytes_array(0).span()); + + // selector: function inc() + let data_increment_counter = [0x37, 0x13, 0x03, 0xc0].span(); + + // When + start_cheat_caller_address(kakarot_core.contract_address, eoa); + + let tx = TxLegacy { + chain_id: Option::Some(chain_id()), + nonce: 0, + to: counter_address.into(), + value, + gas_price, + gas_limit, + input: data_increment_counter + }; + + let (success, _, _) = kakarot_core.eth_send_transaction(Transaction::Legacy(tx)); + assert!(success); + + // Then + // selector: function get() + let data_get_tx = [0x6d, 0x4c, 0xe6, 0x3c].span(); + + // check counter value is 1 + let tx = contract_utils::call_transaction( + chain_id(), Option::Some(counter_address), data_get_tx + ); + let (_, return_data, _) = kakarot_core + .eth_call(origin: evm_address, tx: Transaction::Legacy(tx)); + + // Then + assert_eq!(return_data, u256_to_bytes_array(1).span()); +} + +#[test] +fn test_eth_call() { + // Given + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + let evm_address = test_utils::evm_address(); + kakarot_core.deploy_externally_owned_account(evm_address); + let account = contract_utils::deploy_contract_account( + kakarot_core, test_utils::other_evm_address(), counter_evm_bytecode() + ); + let counter = IAccountDispatcher { contract_address: account.starknet }; + cheat_caller_address( + counter.contract_address, kakarot_core.contract_address, CheatSpan::TargetCalls(1) + ); + counter.write_storage(0, 1); + + let to = Option::Some(test_utils::other_evm_address()); + // selector: function get() + let calldata = [0x6d, 0x4c, 0xe6, 0x3c].span(); + + // When + let tx = contract_utils::call_transaction(chain_id(), to, calldata); + let (success, return_data, _) = kakarot_core + .eth_call(origin: evm_address, tx: Transaction::Legacy(tx)); + + // Then + assert_eq!(success, true); + assert_eq!(return_data, u256_to_bytes_array(1).span()); +} + + +//TODO Needs to be restored by giving the RLP encoding of the transaction to the test +#[test] +#[ignore] +fn test_eth_send_transaction_deploy_tx() { + // Given + let (native_token, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + let evm_address = test_utils::evm_address(); + let eoa = kakarot_core.deploy_externally_owned_account(evm_address); + contract_utils::fund_account_with_native_token( + eoa, native_token, 0xfffffffffffffffffffffffffff + ); + + let gas_limit = test_utils::tx_gas_limit(); + let gas_price = test_utils::gas_price(); + let value = 0; + + // When + // Set the contract address to the EOA address, so that the caller of the + // `eth_send_transaction` + // is an eoa + let tx = TxLegacy { + chain_id: Option::Some(chain_id()), + nonce: 0, + to: Option::None.into(), + value, + gas_price, + gas_limit, + input: deploy_counter_calldata() + }; + cheat_caller_address(kakarot_core.contract_address, eoa, CheatSpan::TargetCalls(1)); + let (_, deploy_result, _) = kakarot_core.eth_send_transaction(Transaction::Legacy(tx)); + + // Then + let expected_address: EthAddress = 0x19587b345dcadfe3120272bd0dbec24741891759 + .try_into() + .unwrap(); + assert_eq!(deploy_result, expected_address.to_bytes().span()); + + // Set back the contract address to Kakarot for the calculation of the deployed SN contract + // address, where we use a kakarot internal functions and thus must "mock" its address. + let computed_sn_addr = kakarot_core.get_starknet_address(expected_address); + let CA = IAccountDispatcher { contract_address: computed_sn_addr }; + let bytecode = CA.bytecode(); + assert_eq!(bytecode, counter_evm_bytecode()); + + // Check that the account was created and `get` returns 0. + let input = [0x6d, 0x4c, 0xe6, 0x3c].span(); + + // No need to set address back to eoa, as eth_call doesn't use the caller address. + let tx = TxLegacy { + chain_id: Option::Some(chain_id()), + nonce: 0, + to: expected_address.into(), + value, + gas_price, + gas_limit, + input + }; + let (_, result, _) = kakarot_core.eth_call(origin: evm_address, tx: Transaction::Legacy(tx)); + // Then + assert(result == u256_to_bytes_array(0).span(), 'wrong result'); +} + + +#[test] +fn test_account_class_hash() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + let uninitialized_account_class_hash = declare("UninitializedAccount") + .unwrap() + .contract_class() + .class_hash; + + let class_hash = kakarot_core.uninitialized_account_class_hash(); + + assert(class_hash == *uninitialized_account_class_hash, 'wrong class hash'); + + let new_class_hash: ClassHash = (*declare("MockContractUpgradeableV1") + .unwrap() + .contract_class() + .class_hash); + start_cheat_caller_address(kakarot_core.contract_address, test_utils::other_starknet_address()); + let mut spy = spy_events(); + kakarot_core.set_account_class_hash(new_class_hash); + + assert(kakarot_core.uninitialized_account_class_hash() == new_class_hash, 'wrong class hash'); + let expected = KakarotCore::Event::AccountClassHashChange( + KakarotCore::AccountClassHashChange { + old_class_hash: class_hash, new_class_hash: new_class_hash + } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(kakarot_core.contract_address) + .build(); + contract_events.assert_emitted(@expected); +} + +#[test] +fn test_account_contract_class_hash() { + let (_, kakarot_core) = contract_utils::setup_contracts_for_testing(); + + let account_contract_class_hash = (*declare("AccountContract") + .unwrap() + .contract_class() + .class_hash); + let class_hash = kakarot_core.get_account_contract_class_hash(); + + assert(class_hash == account_contract_class_hash, 'wrong class hash'); + + let new_class_hash: ClassHash = (*declare("MockContractUpgradeableV1") + .unwrap() + .contract_class() + .class_hash); + start_cheat_caller_address(kakarot_core.contract_address, test_utils::other_starknet_address()); + let mut spy = spy_events(); + kakarot_core.set_account_contract_class_hash(new_class_hash); + assert(kakarot_core.get_account_contract_class_hash() == new_class_hash, 'wrong class hash'); + + let expected = KakarotCore::Event::EOAClassHashChange( + KakarotCore::EOAClassHashChange { + old_class_hash: class_hash, new_class_hash: new_class_hash + } + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(kakarot_core.contract_address) + .build(); + contract_events.assert_emitted(@expected); +} diff --git a/cairo/kakarot-ssj/crates/contracts/tests/test_ownable.cairo b/cairo/kakarot-ssj/crates/contracts/tests/test_ownable.cairo new file mode 100644 index 000000000..d5ac7ca2a --- /dev/null +++ b/cairo/kakarot-ssj/crates/contracts/tests/test_ownable.cairo @@ -0,0 +1,210 @@ +use contracts::components::ownable::{ownable_component}; +use contracts::test_utils::constants::{ZERO, OWNER, OTHER}; +use core::num::traits::Zero; +use core::starknet::ContractAddress; + + +use ownable_component::{InternalImpl, OwnableImpl}; +use snforge_std::{start_cheat_caller_address, spy_events, test_address, EventSpyTrait}; +use snforge_utils::snforge_utils::{EventsFilterBuilderTrait, ContractEventsTrait}; + + +#[starknet::contract] +pub mod MockContract { + use contracts::components::ownable::{ownable_component}; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::Ownable; + + impl OwnableInternalImpl = ownable_component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: ownable_component::Storage + } + + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + OwnableEvent: ownable_component::Event + } +} +type TestingState = ownable_component::ComponentState; + +impl TestingStateDefault of Default { + fn default() -> TestingState { + ownable_component::component_state_for_testing() + } +} + +#[generate_trait] +impl TestingStateImpl of TestingStateTrait { + fn new_with(owner: ContractAddress) -> TestingState { + let mut ownable: TestingState = Default::default(); + ownable.initializer(owner); + ownable + } +} + +#[test] +fn test_ownable_initializer() { + let mut ownable: TestingState = Default::default(); + let test_address: ContractAddress = test_address(); + assert(ownable.owner().is_zero(), 'owner should be zero'); + + let mut spy = spy_events(); + ownable.initializer(OWNER()); + let expected = MockContract::Event::OwnableEvent( + ownable_component::Event::OwnershipTransferred( + ownable_component::OwnershipTransferred { previous_owner: ZERO(), new_owner: OWNER() } + ) + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(test_address) + .build(); + contract_events.assert_emitted(@expected); + assert(ownable.owner() == OWNER(), 'Owner should be set'); +} + +#[test] +fn test_assert_only_owner() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + start_cheat_caller_address(test_address, OWNER()); + + ownable.assert_only_owner(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_assert_only_owner_not_owner() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + start_cheat_caller_address(test_address, OTHER()); + + ownable.assert_only_owner(); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address',))] +fn test_assert_only_owner_zero() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + start_cheat_caller_address(test_address, ZERO()); + ownable.assert_only_owner(); +} + +#[test] +fn test__transfer_ownership() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + + let mut spy = spy_events(); + let expected = MockContract::Event::OwnableEvent( + ownable_component::Event::OwnershipTransferred( + ownable_component::OwnershipTransferred { previous_owner: OWNER(), new_owner: OTHER() } + ) + ); + ownable._transfer_ownership(OTHER()); + + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(test_address) + .build(); + contract_events.assert_emitted(@expected); + assert(ownable.owner() == OTHER(), 'Owner should be OTHER'); +} + + +#[test] +fn test_transfer_ownership() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + start_cheat_caller_address(test_address, OWNER()); + + let mut spy = spy_events(); + ownable.transfer_ownership(OTHER()); + let expected = MockContract::Event::OwnableEvent( + ownable_component::Event::OwnershipTransferred( + ownable_component::OwnershipTransferred { previous_owner: OWNER(), new_owner: OTHER() } + ) + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(test_address) + .build(); + contract_events.assert_emitted(@expected); + + assert(ownable.owner() == OTHER(), 'Should transfer ownership'); +} + +#[test] +#[should_panic(expected: ('New owner is the zero address',))] +fn test_transfer_ownership_to_zero() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + start_cheat_caller_address(test_address, OWNER()); + + ownable.transfer_ownership(ZERO()); +} + + +#[test] +#[should_panic(expected: ('Caller is the zero address',))] +fn test_transfer_ownership_from_zero() { + let mut ownable: TestingState = Default::default(); + + ownable.transfer_ownership(OTHER()); +} + + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_transfer_ownership_from_nonowner() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + start_cheat_caller_address(test_address, OTHER()); + + ownable.transfer_ownership(OTHER()); +} + + +#[test] +fn test_renounce_ownership() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + + start_cheat_caller_address(test_address, OWNER()); + let mut spy = spy_events(); + ownable.renounce_ownership(); + let expected = MockContract::Event::OwnableEvent( + ownable_component::Event::OwnershipTransferred( + ownable_component::OwnershipTransferred { previous_owner: OWNER(), new_owner: ZERO() } + ) + ); + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(test_address) + .build(); + contract_events.assert_emitted(@expected); + + assert(ownable.owner().is_zero(), 'ownership not renounced'); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address',))] +fn test_renounce_ownership_from_zero_address() { + let mut ownable: TestingState = Default::default(); + ownable.renounce_ownership(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_renounce_ownership_from_nonowner() { + let mut ownable: TestingState = TestingStateTrait::new_with(OWNER()); + let test_address: ContractAddress = test_address(); + start_cheat_caller_address(test_address, OTHER()); + + ownable.renounce_ownership(); +} diff --git a/cairo1_contracts/token/.gitignore b/cairo/kakarot-ssj/crates/evm/.gitignore similarity index 100% rename from cairo1_contracts/token/.gitignore rename to cairo/kakarot-ssj/crates/evm/.gitignore diff --git a/cairo/kakarot-ssj/crates/evm/Scarb.toml b/cairo/kakarot-ssj/crates/evm/Scarb.toml new file mode 100644 index 000000000..1180c54a4 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/Scarb.toml @@ -0,0 +1,25 @@ +[package] +name = "evm" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet.workspace = true +utils = { path = "../utils" } +contracts = { path = "../contracts" } +openzeppelin = { path = "../openzeppelin" } +garaga = { git = "https://github.com/keep-starknet-strange/garaga.git" } + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } +snforge_utils = { path = "../snforge_utils" } +assert_macros = "2.8.2" + +[tool] +fmt.workspace = true + +[scripts] +test = "snforge test --max-n-steps 4294967295" +test-profiling = "snforge test --max-n-steps 4294967295 --build-profile" diff --git a/cairo/kakarot-ssj/crates/evm/src/backend.cairo b/cairo/kakarot-ssj/crates/evm/src/backend.cairo new file mode 100644 index 000000000..49f713b44 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/backend.cairo @@ -0,0 +1,2 @@ +pub mod starknet_backend; +pub mod validation; diff --git a/cairo/kakarot-ssj/crates/evm/src/backend/starknet_backend.cairo b/cairo/kakarot-ssj/crates/evm/src/backend/starknet_backend.cairo new file mode 100644 index 000000000..26237c045 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/backend/starknet_backend.cairo @@ -0,0 +1,575 @@ +use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; +use contracts::kakarot_core::eth_rpc::IEthRPC; +use contracts::kakarot_core::{KakarotCore, KakarotCore::KakarotCoreImpl}; +use core::num::traits::zero::Zero; +use core::ops::SnapshotDeref; +use core::starknet::storage::StoragePointerReadAccess; +use core::starknet::syscalls::{deploy_syscall}; +use core::starknet::syscalls::{emit_event_syscall}; +use core::starknet::{EthAddress, get_block_info, SyscallResultTrait}; +use crate::errors::{ensure, EVMError}; +use crate::model::{Address, AddressTrait, Environment, Account, AccountTrait}; +use crate::model::{Transfer}; +use crate::state::{State, StateTrait}; +use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; +use utils::constants::BURN_ADDRESS; +use utils::constants; +use utils::set::SetTrait; + + +/// Commits the state changes to Starknet. +/// +/// # Arguments +/// +/// * `state` - The state to commit. +/// +/// # Returns +/// +/// `Ok(())` if the commit was successful, otherwise an `EVMError`. +pub fn commit(ref state: State) -> Result<(), EVMError> { + commit_accounts(ref state)?; + transfer_native_token(ref state)?; + emit_events(ref state)?; + commit_storage(ref state) +} + +/// Deploys a new EOA contract. +/// +/// # Arguments +/// +/// * `evm_address` - The EVM address of the EOA to deploy. +pub fn deploy(evm_address: EthAddress) -> Result { + // Unlike CAs, there is not check for the existence of an EOA prealably to calling + // `EOATrait::deploy` - therefore, we need to check that there is no collision. + let mut is_deployed = evm_address.is_deployed(); + ensure(!is_deployed, EVMError::Collision)?; + + let mut kakarot_state = KakarotCore::unsafe_new_contract_state(); + let uninitialized_account_class_hash = kakarot_state.uninitialized_account_class_hash(); + let calldata: Span = [1, evm_address.into()].span(); + + let (starknet_address, _) = deploy_syscall( + uninitialized_account_class_hash, + contract_address_salt: evm_address.into(), + calldata: calldata, + deploy_from_zero: false + ) + .unwrap_syscall(); + + Result::Ok(Address { evm: evm_address, starknet: starknet_address }) +} + +pub fn get_bytecode(evm_address: EthAddress) -> Span { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + let starknet_address = kakarot_state.address_registry(evm_address); + if starknet_address.is_non_zero() { + let account = IAccountDispatcher { contract_address: starknet_address }; + account.bytecode() + } else { + [].span() + } +} + +/// Populate an Environment with Starknet syscalls. +pub fn get_env(origin: Address, gas_price: u128) -> Environment { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + let kakarot_storage = kakarot_state.snapshot_deref(); + let block_info = get_block_info().unbox(); + + // tx.gas_price and env.gas_price have the same values here + // - this is not always true in EVM transactions + Environment { + origin: origin, + gas_price, + chain_id: kakarot_state.eth_chain_id(), + prevrandao: kakarot_storage.Kakarot_prev_randao.read(), + block_number: block_info.block_number, + block_gas_limit: constants::BLOCK_GAS_LIMIT, + block_timestamp: block_info.block_timestamp, + coinbase: kakarot_storage.Kakarot_coinbase.read(), + base_fee: kakarot_storage.Kakarot_base_fee.read(), + state: Default::default(), + } +} + +/// Fetches the value stored at the given key for the corresponding contract accounts. +/// If the account is not deployed (in case of a create/deploy transaction), returns 0. +/// # Arguments +/// +/// * `account` The account to read from. +/// * `key` The key to read. +/// +/// # Returns +/// +/// A `Result` containing the value stored at the given key or an `EVMError` if there was an error. +pub fn fetch_original_storage(account: @Account, key: u256) -> u256 { + let is_deployed = account.evm_address().is_deployed(); + if is_deployed { + return IAccountDispatcher { contract_address: account.starknet_address() }.storage(key); + } + 0 +} + +/// Fetches the balance of the given address. +/// +/// # Arguments +/// +/// * `self` - The address to fetch the balance of. +/// +/// # Returns +/// +/// The balance of the given address. +pub fn fetch_balance(self: @Address) -> u256 { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + kakarot_state.eth_get_balance(*self.evm) +} + + +/// Commits the account changes to Starknet. +/// +/// # Arguments +/// +/// * `state` - The state containing the accounts to commit. +/// +/// # Returns +/// +/// `Ok(())` if the commit was successful, otherwise an `EVMError`. +fn commit_accounts(ref state: State) -> Result<(), EVMError> { + let mut account_keys = state.accounts.keyset.to_span(); + for evm_address in account_keys { + let account = state.accounts.changes.get(*evm_address).deref(); + commit_account(@account, ref state); + }; + return Result::Ok(()); +} + +/// Commits the account to Starknet by updating the account state if it +/// exists, or deploying a new account if it doesn't. +/// +/// # Arguments +/// * `self` - The account to commit +/// * `state` - The state, modified in the case of selfdestruct transfers +/// +/// # Returns +/// +/// `Ok(())` if the commit was successful, otherwise an `EVMError`. +fn commit_account(self: @Account, ref state: State) { + if self.evm_address().is_precompile() { + return; + } + + // Case new account + if !self.evm_address().is_deployed() { + deploy(self.evm_address()).expect('account deployment failed'); + } + + // @dev: EIP-6780 - If selfdestruct on an account created, dont commit data + // and burn any leftover balance. + if (self.is_selfdestruct() && self.is_created()) { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + let burn_starknet_address = kakarot_state + .get_starknet_address(BURN_ADDRESS.try_into().unwrap()); + let burn_address = Address { + starknet: burn_starknet_address, evm: BURN_ADDRESS.try_into().unwrap() + }; + state + .add_transfer( + Transfer { sender: self.address(), recipient: burn_address, amount: self.balance() } + ) + .expect('Failed to burn on selfdestruct'); + return; + } + + if !self.has_code_or_nonce() { + // Nothing to commit + return; + } + + // Write updated nonce and storage + //TODO: storage commits are done in the State commitment as they're not part of the account + //model in SSJ + let starknet_account = IAccountDispatcher { contract_address: self.starknet_address() }; + starknet_account.set_nonce(*self.nonce); + + //Storage is handled outside of the account and must be committed after all accounts are + //committed. + if self.is_created() { + starknet_account.write_bytecode(self.bytecode()); + starknet_account.set_code_hash(self.code_hash()); + //TODO: save valid jumpdests https://github.com/kkrt-labs/kakarot-ssj/issues/839 + } + return; +} + +/// Iterates through the list of pending transfer and triggers them +fn transfer_native_token(ref self: State) -> Result<(), EVMError> { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + let native_token = kakarot_state.get_native_token(); + while let Option::Some(transfer) = self.transfers.pop_front() { + IERC20CamelDispatcher { contract_address: native_token } + .transferFrom(transfer.sender.starknet, transfer.recipient.starknet, transfer.amount); + }; + Result::Ok(()) +} + +/// Iterates through the list of events and emits them. +fn emit_events(ref self: State) -> Result<(), EVMError> { + while let Option::Some(event) = self.events.pop_front() { + let mut keys = Default::default(); + let mut data = Default::default(); + Serde::>::serialize(@event.keys, ref keys); + Serde::>::serialize(@event.data, ref data); + emit_event_syscall(keys.span(), data.span()).unwrap_syscall(); + }; + return Result::Ok(()); +} + +/// Commits storage changes to the KakarotCore contract by writing pending +/// state changes to Starknet Storage. +/// commit_storage MUST be called after commit_accounts. +fn commit_storage(ref self: State) -> Result<(), EVMError> { + let mut storage_keys = self.accounts_storage.keyset.to_span(); + for state_key in storage_keys { + let (evm_address, key, value) = self.accounts_storage.changes.get(*state_key).deref(); + let mut account = self.get_account(evm_address); + // @dev: EIP-6780 - If selfdestruct on an account created, dont commit data + if account.is_selfdestruct() && account.is_created() { + continue; + } + IAccountDispatcher { contract_address: account.starknet_address() } + .write_storage(key, value); + }; + Result::Ok(()) +} + +#[cfg(test)] +mod tests { + use core::starknet::{ClassHash}; + use crate::backend::starknet_backend; + use crate::model::account::Account; + use crate::model::{Address, Event}; + use crate::state::{State, StateTrait}; + use crate::test_utils::{ + setup_test_environment, uninitialized_account, account_contract, register_account + }; + use crate::test_utils::{evm_address}; + use snforge_std::{ + test_address, start_mock_call, get_class_hash, spy_events, EventSpyTrait, + Event as StarknetEvent + }; + use snforge_utils::snforge_utils::{ + assert_not_called, assert_called, EventsFilterBuilderTrait, ContractEventsTrait + }; + use super::{commit_storage, emit_events}; + use utils::helpers::compute_starknet_address; + use utils::traits::bytes::U8SpanExTrait; + + // Helper function to create a test account + fn create_test_account(is_selfdestruct: bool, is_created: bool, id: felt252) -> Account { + let evm_address = (evm_address().into() + id).try_into().unwrap(); + let starknet_address = (0x5678 + id).try_into().unwrap(); + Account { + address: Address { evm: evm_address, starknet: starknet_address }, + nonce: 0, + code: [].span(), + code_hash: 0, + balance: 0, + selfdestruct: is_selfdestruct, + is_created: is_created, + } + } + + // Implementation to convert an `Event` into a serialized `StarknetEvent` + impl EventIntoStarknetEvent of Into { + fn into(self: Event) -> StarknetEvent { + let mut serialized_keys = array![]; + let mut serialized_data = array![]; + Serde::>::serialize(@self.keys, ref serialized_keys); + Serde::>::serialize(@self.data, ref serialized_data); + StarknetEvent { keys: serialized_keys, data: serialized_data } + } + } + + + mod test_commit_storage { + use snforge_std::start_mock_call; + use snforge_utils::snforge_utils::{assert_called_with, assert_not_called}; + use super::{create_test_account, StateTrait, commit_storage}; + + #[test] + fn test_commit_storage_normal_case() { + let mut state = Default::default(); + let account = create_test_account(false, false, 0); + state.set_account(account); + + let key = 0x100; + let value = 0x200; + state.write_state(account.address.evm, key, value); + + // Mock the write_storage call + start_mock_call::<()>(account.address.starknet, selector!("write_storage"), ()); + + commit_storage(ref state).expect('commit storage failed'); + + //TODO(starknet-foundry): verify call args in assert_called + assert_called_with::< + (u256, u256) + >(account.address.starknet, selector!("write_storage"), (key, value)); + } + + #[test] + fn test_commit_storage_selfdestruct_and_created() { + let mut state = Default::default(); + let account = create_test_account(true, true, 0); + state.set_account(account); + + let key = 0x100; + let value = 0x200; + state.write_state(account.address.evm, key, value); + + // Mock the write_storage call + start_mock_call::<()>(account.address.starknet, selector!("write_storage"), ()); + + commit_storage(ref state).expect('commit storage failed'); + + // Assert that write_storage was not called + assert_not_called(account.address.starknet, selector!("write_storage")); + } + + #[test] + fn test_commit_storage_only_selfdestruct() { + let mut state = Default::default(); + let account = create_test_account(true, false, 0); + state.set_account(account); + + let key = 0x100; + let value = 0x200; + state.write_state(account.address.evm, key, value); + + // Mock the write_storage call + start_mock_call::<()>(account.address.starknet, selector!("write_storage"), ()); + + commit_storage(ref state).expect('commit storage failed'); + + // Assert that write_storage was called + assert_called_with::< + (u256, u256) + >(account.address.starknet, selector!("write_storage"), (key, value)); + } + + #[test] + fn test_commit_storage_multiple_accounts() { + let mut state = Default::default(); + + // Account 0: Normal + let account0 = create_test_account(false, false, 0); + state.set_account(account0); + + // Account 1: Selfdestruct and created + let account1 = create_test_account(true, true, 1); + state.set_account(account1); + + // Account 2: Only selfdestruct + let account2 = create_test_account(true, false, 2); + state.set_account(account2); + + // Set storage for all accounts + let key = 0x100; + let value = 0x200; + state.write_state(account0.address.evm, key, value); + state.write_state(account1.address.evm, key, value); + state.write_state(account2.address.evm, key, value); + + // Mock the write_storage calls + start_mock_call::<()>(account0.address.starknet, selector!("write_storage"), ()); + start_mock_call::<()>(account1.address.starknet, selector!("write_storage"), ()); + start_mock_call::<()>(account2.address.starknet, selector!("write_storage"), ()); + + commit_storage(ref state).expect('commit storage failed'); + + // Assert that write_storage was called for accounts 1 and 3, but not for account 2 + assert_called_with::< + (u256, u256) + >(account0.address.starknet, selector!("write_storage"), (key, value)); + assert_not_called(account1.address.starknet, selector!("write_storage")); + assert_called_with::< + (u256, u256) + >(account2.address.starknet, selector!("write_storage"), (key, value)); + } + } + + #[test] + #[ignore] + //TODO(starknet-fonudry): it's impossible to deploy an un-declared class, nor is it possible to + //mock_deploy. + fn test_deploy() { + // store the classes in the context of the local execution, to be used for deploying the + // account class + setup_test_environment(); + let test_address = test_address(); + + start_mock_call::< + ClassHash + >(test_address, selector!("get_account_contract_class_hash"), account_contract()); + start_mock_call::<()>(test_address, selector!("initialize"), ()); + let eoa_address = starknet_backend::deploy(evm_address()) + .expect('deployment of EOA failed'); + + let class_hash = get_class_hash(eoa_address.starknet); + assert_eq!(class_hash, account_contract()); + } + + #[test] + #[ignore] + //TODO(starknet-foundry): it's impossible to deploy an un-declared class, nor is it possible to + //mock_deploy. + fn test_account_commit_undeployed_create_should_change_set_all() { + setup_test_environment(); + let test_address = test_address(); + let evm_address = evm_address(); + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); + + let mut state: State = Default::default(); + + // When + let bytecode = [0x1].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let mut account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, + nonce: 420, + code: bytecode, + code_hash: code_hash, + balance: 0, + selfdestruct: false, + is_created: true, + }; + state.set_account(account); + + start_mock_call::<()>(starknet_address, selector!("set_nonce"), ()); + start_mock_call::< + ClassHash + >(test_address, selector!("get_account_contract_class_hash"), account_contract()); + starknet_backend::commit(ref state).expect('commitment failed'); + + // Then + //TODO(starknet-foundry): we should be able to assert this has been called with specific + //data, to pass in mock_call + assert_called(starknet_address, selector!("set_nonce")); + assert_not_called(starknet_address, selector!("write_bytecode")); + } + + #[test] + fn test_account_commit_deployed_and_created_should_write_code() { + setup_test_environment(); + let test_address = test_address(); + let evm_address = evm_address(); + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); + register_account(evm_address, starknet_address); + + let mut state: State = Default::default(); + let bytecode = [0x1].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let mut account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, + nonce: 420, + code: bytecode, + code_hash: code_hash, + balance: 0, + selfdestruct: false, + is_created: true, + }; + state.set_account(account); + + start_mock_call::<()>(starknet_address, selector!("write_bytecode"), ()); + start_mock_call::<()>(starknet_address, selector!("set_code_hash"), ()); + start_mock_call::<()>(starknet_address, selector!("set_nonce"), ()); + starknet_backend::commit(ref state).expect('commitment failed'); + + // Then the account should have a new code. + //TODO(starknet-foundry): we should be able to assert this has been called with specific + //data, to pass in mock_call + assert_called(starknet_address, selector!("write_bytecode")); + assert_called(starknet_address, selector!("set_code_hash")); + assert_called(starknet_address, selector!("set_nonce")); + } + + #[test] + fn test_emit_events() { + // Initialize the state + let mut state: State = Default::default(); + + // Prepare a list of events with different combinations of keys and data + let evm_events = array![ + Event { keys: array![], data: array![] }, // Empty event + Event { keys: array![1.into()], data: array![2, 3] }, // Single key, multiple data + Event { + keys: array![4.into(), 5.into()], data: array![6] + }, // Multiple keys, single data + Event { + keys: array![7.into(), 8.into(), 9.into()], data: array![10, 11, 12, 13] + } // Multiple keys and data + ]; + + // Add each event to the state + for event in evm_events.clone() { + state.add_event(event); + }; + + // Emit the events and assert that no events are left in the state + let mut spy = spy_events(); + emit_events(ref state).expect('emit events failed'); + assert!(state.events.is_empty()); + + // Capture emitted events + let contract_events = EventsFilterBuilderTrait::from_events(@spy.get_events()) + .with_contract_address(test_address()) + .build(); + + // Assert that each original event was emitted as expected + for event in evm_events { + let starknet_event = EventIntoStarknetEvent::into( + event + ); // Convert to StarkNet event format + contract_events.assert_emitted(@starknet_event); + }; + } +} +// #[test] +// #[ignore] +//TODO(starknet-foundry): it's impossible to deploy an un-declared class, nor is it possible to +//mock_deploy. +// fn test_exec_sstore_finalized() { // // Given +// setup_test_environment(); +// let mut vm = VMBuilderTrait::new_with_presets().build(); +// let evm_address = vm.message().target.evm; +// let starknet_address = compute_starknet_address( +// test_address(), evm_address, uninitialized_account() +// ); +// let account = Account { +// address: Address { evm: evm_address, starknet: starknet_address }, +// code: [].span(), +// nonce: 1, +// balance: 0, +// selfdestruct: false, +// is_created: false, +// }; +// let key: u256 = 0x100000000000000000000000000000001; +// let value: u256 = 0xABDE1E11A5; +// vm.stack.push(value).expect('push failed'); +// vm.stack.push(key).expect('push failed'); + +// // When + +// vm.exec_sstore().expect('exec_sstore failed'); +// starknet_backend::commit(ref vm.env.state).expect('commit storage failed'); + +// // Then +// assert(fetch_original_storage(@account, key) == value, 'wrong committed value') +// } +// } + + diff --git a/cairo/kakarot-ssj/crates/evm/src/backend/validation.cairo b/cairo/kakarot-ssj/crates/evm/src/backend/validation.cairo new file mode 100644 index 000000000..30414f5ed --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/backend/validation.cairo @@ -0,0 +1,322 @@ +use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; +use contracts::kakarot_core::KakarotCore; +use contracts::kakarot_core::eth_rpc::IEthRPC; +use core::num::traits::Bounded; +use core::ops::SnapshotDeref; +use core::starknet::storage::{StoragePointerReadAccess}; +use core::starknet::{get_caller_address}; +use crate::gas; +use starknet::storage::StorageTrait; +use utils::eth_transaction::check_gas_fee; +use utils::eth_transaction::transaction::{Transaction, TransactionTrait}; + +/// Validates the ethereum transaction by checking adherence to Ethereum rules regarding +/// Gas logic, nonce, chainId and required balance. +/// +/// # Returns +/// +/// * The intrinsic gas cost of the transaction +pub fn validate_eth_tx(kakarot_state: @KakarotCore::ContractState, tx: Transaction) -> u64 { + let kakarot_storage = kakarot_state.snapshot_deref().storage(); + // Validate transaction + + // Validate chain_id for post eip155 + let tx_chain_id = tx.chain_id(); + let kakarot_chain_id: u64 = kakarot_state.eth_chain_id(); + if (tx_chain_id.is_some()) { + assert(tx_chain_id.unwrap() == kakarot_chain_id, 'Invalid chain id'); + } + + // Validate nonce + let starknet_caller_address = get_caller_address(); + let account = IAccountDispatcher { contract_address: starknet_caller_address }; + let account_nonce = account.get_nonce(); + assert(account_nonce == tx.nonce(), 'Invalid nonce'); + assert(account_nonce != Bounded::::MAX, 'Nonce overflow'); + + // Validate gas + let gas_limit = tx.gas_limit(); + assert(gas_limit <= kakarot_storage.Kakarot_block_gas_limit.read(), 'Tx gas > Block gas'); + let block_base_fee = kakarot_storage.Kakarot_base_fee.read(); + let gas_fee_check = check_gas_fee( + tx.max_fee_per_gas(), tx.max_priority_fee_per_gas(), block_base_fee.into() + ); + assert!(gas_fee_check.is_ok(), "{:?}", gas_fee_check.unwrap_err()); + + // Intrinsic Gas + let intrinsic_gas = gas::calculate_intrinsic_gas_cost(@tx); + assert(gas_limit >= intrinsic_gas, 'Intrinsic gas > gas limit'); + + // Validate balance + let balance = kakarot_state.eth_get_balance(account.get_evm_address()); + let max_gas_fee = tx.gas_limit().into() * tx.max_fee_per_gas(); + let tx_cost = tx.value() + max_gas_fee.into(); + assert(tx_cost <= balance, 'Not enough ETH'); + intrinsic_gas +} + +#[cfg(test)] +mod tests { + use contracts::kakarot_core::KakarotCore; + use core::num::traits::Bounded; + use core::ops::SnapshotDeref; + use core::starknet::storage::{StorageTrait, StoragePathEntry}; + use core::starknet::{EthAddress, ContractAddress}; + use evm::gas; + use snforge_std::cheatcodes::storage::{store_felt252}; + use snforge_std::{ + start_mock_call, test_address, start_cheat_chain_id_global, store, + start_cheat_caller_address, mock_call + }; + use super::validate_eth_tx; + use utils::constants::BLOCK_GAS_LIMIT; + use utils::eth_transaction::common::TxKind; + use utils::eth_transaction::eip1559::TxEip1559; + use utils::eth_transaction::legacy::TxLegacy; + use utils::eth_transaction::transaction::Transaction; + + fn set_up() -> (KakarotCore::ContractState, ContractAddress) { + // Define the addresses used in the tests, whose calls will be mocked + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + let kakarot_storage = kakarot_state.snapshot_deref().storage(); + let kakarot_address = test_address(); + let account_evm_address: EthAddress = 'account_evm_address'.try_into().unwrap(); + let account_starknet_address = 'account_starknet_address'.try_into().unwrap(); + let native_token_address = 'native_token_address'.try_into().unwrap(); + + // Set up the environment + start_cheat_chain_id_global(1); + let base_fee_storage = kakarot_storage.Kakarot_base_fee.__base_address__; + let block_gas_limit_storage = kakarot_storage.Kakarot_block_gas_limit.__base_address__; + let native_token_storage_address = kakarot_storage + .Kakarot_native_token_address + .__base_address__; + store_felt252(kakarot_address, base_fee_storage, 1_000_000_000); // 1 Gwei + store_felt252(kakarot_address, block_gas_limit_storage, BLOCK_GAS_LIMIT.into()); + store_felt252(kakarot_address, native_token_storage_address, native_token_address.into()); + let map_entry_address = kakarot_storage + .Kakarot_evm_to_starknet_address + .entry(account_evm_address) + .deref() + .__storage_pointer_address__; + store( + kakarot_address, + map_entry_address.into(), + array![account_starknet_address.into()].span() + ); + + // Mock the calls to the account contract and the native token contract + start_cheat_caller_address(kakarot_address, account_starknet_address); + start_mock_call(account_starknet_address, selector!("get_nonce"), 0); + start_mock_call( + account_starknet_address, selector!("get_evm_address"), account_evm_address + ); + start_mock_call( + native_token_address, selector!("balanceOf"), Bounded::::MAX + ); // Min to pay for gas + value + + (kakarot_state, native_token_address) + } + + #[test] + fn test_validate_eth_tx_typical_case() { + // Setup the environment + let (kakarot_state, _) = set_up(); + + // Create a transaction object for the test + let tx = Transaction::Eip1559( + TxEip1559 { + chain_id: 1, // Should match the chain_id in the environment + nonce: 0, + max_priority_fee_per_gas: 1_000_000_000, // 1 Gwei + max_fee_per_gas: 2_000_000_000, // 2 Gwei + gas_limit: 21000, // Standard gas limit for a simple transfer + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // 1 ETH + input: array![].span(), + access_list: array![].span(), + } + ); + + // Test that the function performs validation and assert expected results + let intrinsic_gas = validate_eth_tx(@kakarot_state, tx); + + assert_eq!(intrinsic_gas, 21000); // Standard intrinsic gas for a simple transfer + } + + #[test] + #[should_panic(expected: ('Invalid chain id',))] + fn test_validate_eth_tx_invalid_chain_id() { + // Setup the environment + let (kakarot_state, _) = set_up(); + + // Create a transaction object for the test + let tx = Transaction::Eip1559( + TxEip1559 { + chain_id: 600, // wrong chain_id + nonce: 0, + max_priority_fee_per_gas: 1_000_000_000, // 1 Gwei + max_fee_per_gas: 2_000_000_000, // 2 Gwei + gas_limit: 21000, // Standard gas limit for a simple transfer + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // 1 ETH + input: array![].span(), + access_list: array![].span(), + } + ); + + // Test that the function performs validation and assert expected results + validate_eth_tx(@kakarot_state, tx); + } + + #[test] + #[should_panic(expected: ('Invalid nonce',))] + fn test_validate_eth_tx_invalid_nonce() { + // Setup the environment + let (kakarot_state, _) = set_up(); + + // Create a transaction object for the test + let tx = Transaction::Eip1559( + TxEip1559 { + chain_id: 1, // Should match the chain_id in the environment + nonce: 600, //Invalid nonce + max_priority_fee_per_gas: 1_000_000_000, // 1 Gwei + max_fee_per_gas: 2_000_000_000, // 2 Gwei + gas_limit: 21000, // Standard gas limit for a simple transfer + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // 1 ETH + input: array![].span(), + access_list: array![].span(), + } + ); + + // Test that the function performs validation and assert expected results + validate_eth_tx(@kakarot_state, tx); + } + + #[test] + #[should_panic(expected: ('Tx gas > Block gas',))] + fn test_validate_eth_tx_gas_limit_exceeds_block_gas_limit() { + // Setup the environment + let (kakarot_state, _) = set_up(); + + // Create a transaction object for the test + let tx = Transaction::Eip1559( + TxEip1559 { + chain_id: 1, // Should match the chain_id in the environment + nonce: 0, + max_priority_fee_per_gas: 1_000_000_000, // 1 Gwei + max_fee_per_gas: 2_000_000_000, // 2 Gwei + gas_limit: BLOCK_GAS_LIMIT + 1, // Gas limit greater than block gas limit + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // 1 ETH + input: array![].span(), + access_list: array![].span(), + } + ); + + // Test that the function performs validation and assert expected results + validate_eth_tx(@kakarot_state, tx); + } + + #[test] + #[should_panic(expected: ('Intrinsic gas > gas limit',))] + fn test_validate_eth_tx_intrinsic_gas_exceeds_gas_limit() { + // Setup the environment + let (kakarot_state, _) = set_up(); + + // Create a transaction object for the test + let mut tx = Transaction::Eip1559( + TxEip1559 { + chain_id: 1, // Should match the chain_id in the environment + nonce: 0, + max_priority_fee_per_gas: 1_000_000_000, // 1 Gwei + max_fee_per_gas: 2_000_000_000, // 2 Gwei + gas_limit: 0, + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // 1 ETH + input: array![].span(), + access_list: array![].span(), + } + ); + + // Test that the function performs validation and assert expected results + validate_eth_tx(@kakarot_state, tx); + } + + #[test] + #[should_panic(expected: ('Not enough ETH',))] + fn test_validate_eth_tx_insufficient_balance() { + // Setup the environment + let (kakarot_state, native_token_address) = set_up(); + + // Create a transaction object for the test + let tx = Transaction::Eip1559( + TxEip1559 { + chain_id: 1, // Should match the chain_id in the environment + nonce: 0, + max_priority_fee_per_gas: 1_000_000_000, // 1 Gwei + max_fee_per_gas: 2_000_000_000, // 2 Gwei + gas_limit: 21000, // Standard gas limit for a simple transfer + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // 1 ETH + input: array![].span(), + access_list: array![].span(), + } + ); + + start_mock_call(native_token_address, selector!("balanceOf"), Bounded::::MIN); + + // Test that the function performs validation and assert expected results + validate_eth_tx(@kakarot_state, tx); + } + + #[test] + fn test_validate_eth_tx_max_gas_limit() { + // Setup the environment + let (kakarot_state, _) = set_up(); + + // Create a transaction object for the test + let tx = Transaction::Eip1559( + TxEip1559 { + chain_id: 1, // Should match the chain_id in the environment + nonce: 0, + max_priority_fee_per_gas: 1_000_000_000, // 1 Gwei + max_fee_per_gas: 2_000_000_000, // 2 Gwei + gas_limit: BLOCK_GAS_LIMIT, // Gas limit = Max block gas limit + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // 1 ETH + input: array![].span(), + access_list: array![].span(), + } + ); + + // Test that the function performs validation and assert expected results + let intrinsic_gas = validate_eth_tx(@kakarot_state, tx); + assert_eq!(intrinsic_gas, 21000); // Standard intrinsic gas for a simple transfer + } + + #[test] + fn test_validate_eth_tx_pre_eip155() { + // Setup the environment + let (kakarot_state, _) = set_up(); + + // Create a transaction object for the test + let tx = Transaction::Legacy( + TxLegacy { + chain_id: Option::None, + nonce: 0, + gas_price: 120000000000000000000000000, + gas_limit: 21000, + to: TxKind::Call(0x1234567890123456789012345678901234567890.try_into().unwrap()), + value: 1000000000000000000_u256, // Standard gas limit for a simple transfer + input: array![].span(), + } + ); + + // Test that the function performs validation and assert expected results + let intrinsic_gas = validate_eth_tx(@kakarot_state, tx); + + assert_eq!(intrinsic_gas, 21000); // Standard intrinsic gas for a simple transfer + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/call_helpers.cairo b/cairo/kakarot-ssj/crates/evm/src/call_helpers.cairo new file mode 100644 index 000000000..2c38fbbf0 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/call_helpers.cairo @@ -0,0 +1,127 @@ +use contracts::kakarot_core::KakarotCore; +use contracts::kakarot_core::interface::IKakarotCore; +//! CALL, CALLCODE, DELEGATECALL, STATICCALL opcode helpers +use core::cmp::min; +use core::starknet::EthAddress; + +use crate::errors::EVMError; +use crate::interpreter::EVMTrait; +use crate::memory::MemoryTrait; +use crate::model::vm::{VM, VMTrait}; +use crate::model::{Address, Message, ExecutionResultStatus}; +use crate::stack::StackTrait; +use crate::state::StateTrait; +use utils::constants; +use utils::set::SetTrait; +use utils::traits::{BoolIntoNumeric, U256TryIntoResult}; + +/// CallArgs is a subset of CallContext +/// Created in order to simplify setting up the call opcodes +#[derive(Drop, PartialEq)] +pub struct CallArgs { + caller: Address, + code_address: Address, + to: Address, + gas: u128, + value: u256, + calldata: Span, + ret_offset: usize, + ret_size: usize, + read_only: bool, + should_transfer: bool, + max_memory_size: usize, +} + +#[derive(Drop)] +pub enum CallType { + Call, + DelegateCall, + CallCode, + StaticCall, +} + +#[generate_trait] +pub impl CallHelpersImpl of CallHelpers { + /// Initializes and enters into a new sub-context + /// The Machine will change its `current_ctx` to point to the + /// newly created sub-context. + /// Then, the EVM execution loop will start on this new execution context. + fn generic_call( + ref self: VM, + gas: u64, + value: u256, + caller: EthAddress, + to: EthAddress, + code_address: EthAddress, + should_transfer_value: bool, + is_staticcall: bool, + args_offset: usize, + args_size: usize, + ret_offset: usize, + ret_size: usize + ) -> Result<(), EVMError> { + self.return_data = [].span(); + if self.message().depth >= constants::STACK_MAX_DEPTH { + self.gas_left += gas; + return self.stack.push(0); + } + + let mut calldata = Default::default(); + self.memory.load_n(args_size, ref calldata, args_offset); + + // We enter the standard flow + let code_account = self.env.state.get_account(code_address); + let read_only = is_staticcall || self.message.read_only; + + let kakarot_core = KakarotCore::unsafe_new_contract_state(); + let to = Address { evm: to, starknet: kakarot_core.get_starknet_address(to) }; + let caller = Address { evm: caller, starknet: kakarot_core.get_starknet_address(caller) }; + + let message = Message { + caller, + target: to, + gas_limit: gas, + data: calldata.span(), + code: code_account.code, + code_address: code_account.address, + value: value, + should_transfer_value: should_transfer_value, + depth: self.message().depth + 1, + read_only: read_only, + accessed_addresses: self.accessed_addresses.clone().spanset(), + accessed_storage_keys: self.accessed_storage_keys.clone().spanset(), + }; + + let result = EVMTrait::process_message(message, ref self.env); + self.merge_child(@result); + + match result.status { + ExecutionResultStatus::Success => { + self.return_data = result.return_data; + self.stack.push(1)?; + }, + ExecutionResultStatus::Revert => { + self.return_data = result.return_data; + self.stack.push(0)?; + }, + ExecutionResultStatus::Exception => { + // If the call has halted exceptionally, + // the return_data is emptied, and nothing is stored in memory + self.return_data = [].span(); + self.stack.push(0)?; + return Result::Ok(()); + }, + } + + // Get the min between len(return_data) and call_ctx.ret_size. + let actual_returndata_len = min(result.return_data.len(), ret_size); + + let actual_return_data = result.return_data.slice(0, actual_returndata_len); + // TODO: Check if need to pad the memory with zeroes if result.return_data.len() < + // call_ctx.ret_size and memory is not empty at offset call_args.ret_offset + + // result.return_data.len() + self.memory.store_n(actual_return_data, ret_offset); + + Result::Ok(()) + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/create_helpers.cairo b/cairo/kakarot-ssj/crates/evm/src/create_helpers.cairo new file mode 100644 index 000000000..14c3d435c --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/create_helpers.cairo @@ -0,0 +1,193 @@ +use core::num::traits::Bounded; +use core::num::traits::CheckedAdd; +use core::num::traits::Zero; +use core::starknet::EthAddress; +use crate::errors::{ensure, EVMError}; +use crate::gas; +use crate::interpreter::EVMTrait; +use crate::memory::MemoryTrait; +use crate::model::Message; +use crate::model::account::{Account, AccountTrait}; +use crate::model::vm::{VM, VMTrait}; +use crate::model::{ExecutionResult, ExecutionResultTrait, ExecutionResultStatus}; +use crate::stack::StackTrait; +use crate::state::StateTrait; +use utils::address::{compute_contract_address, compute_create2_contract_address}; +use utils::constants; +use utils::helpers::bytes_32_words_size; +use utils::set::SetTrait; +use utils::traits::{ + BoolIntoNumeric, EthAddressIntoU256, U256TryIntoResult, SpanU8TryIntoResultEthAddress +}; +/// Helper struct to prepare CREATE and CREATE2 opcodes +#[derive(Drop)] +pub struct CreateArgs { + to: EthAddress, + value: u256, + bytecode: Span, +} + +#[derive(Copy, Drop)] +pub enum CreateType { + Create, + Create2, +} + +#[generate_trait] +pub impl CreateHelpersImpl of CreateHelpers { + /// Prepare the initialization of a new child or so-called sub-context + /// As part of the CREATE family of opcodes. + fn prepare_create(ref self: VM, create_type: CreateType) -> Result { + let value = self.stack.pop()?; + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + let init_code_gas = gas::init_code_cost(size); + let charged_gas = match create_type { + CreateType::Create => gas::CREATE + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(init_code_gas) + .ok_or(EVMError::OutOfGas)?, + CreateType::Create2 => { + let calldata_words = bytes_32_words_size(size); + gas::CREATE + .checked_add(gas::KECCAK256WORD * calldata_words.into()) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(init_code_gas) + .ok_or(EVMError::OutOfGas)? + }, + }; + self.charge_gas(charged_gas)?; + + let mut bytecode = Default::default(); + self.memory.load_n(size, ref bytecode, offset); + + let to = match create_type { + CreateType::Create => { + let nonce = self.env.state.get_account(self.message().target.evm).nonce(); + compute_contract_address(self.message().target.evm, sender_nonce: nonce) + }, + CreateType::Create2 => compute_create2_contract_address( + self.message().target.evm, salt: self.stack.pop()?, bytecode: bytecode.span() + )?, + }; + + Result::Ok(CreateArgs { to, value, bytecode: bytecode.span() }) + } + + + /// Initializes and enters into a new sub-context + /// The Machine will change its `current_ctx` to point to the + /// newly created sub-context. + /// Then, the EVM execution loop will start on this new execution context. + fn generic_create(ref self: VM, create_args: CreateArgs) -> Result<(), EVMError> { + self.accessed_addresses.add(create_args.to); + + let create_message_gas = gas::max_message_call_gas(self.gas_left); + self.gas_left -= create_message_gas; + + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + self.return_data = [].span(); + + // The sender in the subcontext is the message's target + let sender_address = self.message().target; + let mut sender = self.env.state.get_account(sender_address.evm); + let sender_current_nonce = sender.nonce(); + if sender.balance() < create_args.value + || sender_current_nonce == Bounded::::MAX + || self.message.depth == constants::STACK_MAX_DEPTH { + self.gas_left += create_message_gas; + return self.stack.push(0); + } + + sender + .set_nonce( + sender_current_nonce + 1 + ); // Will not overflow because of the previous check. + self.env.state.set_account(sender); + + let mut target_account = self.env.state.get_account(create_args.to); + let target_address = target_account.address(); + // Collision happens if the target account loaded in state has code or nonce set, meaning + // - it's deployed on SN and is an active EVM contract + // - it's not deployed on SN and is an active EVM contract in the Kakarot cache + if target_account.has_code_or_nonce() { + return self.stack.push(0); + }; + + ensure(create_args.bytecode.len() <= constants::MAX_INITCODE_SIZE, EVMError::OutOfGas)?; + + let child_message = Message { + caller: sender_address, + target: target_address, + gas_limit: create_message_gas, + data: [].span(), + code: create_args.bytecode, + code_address: Zero::zero(), + value: create_args.value, + should_transfer_value: true, + depth: self.message().depth + 1, + read_only: false, + accessed_addresses: self.accessed_addresses.clone().spanset(), + accessed_storage_keys: self.accessed_storage_keys.clone().spanset(), + }; + + let result = EVMTrait::process_create_message(child_message, ref self.env); + self.merge_child(@result); + + match result.status { + ExecutionResultStatus::Success => { + self.return_data = [].span(); + self.stack.push(target_address.evm.into())?; + }, + ExecutionResultStatus::Revert => { + self.return_data = result.return_data; + self.stack.push(0)?; + }, + ExecutionResultStatus::Exception => { + // returndata is emptied in case of exception + self.return_data = [].span(); + self.stack.push(0)?; + }, + } + Result::Ok(()) + } + + /// Finalizes the creation of an account contract by + /// setting its code and charging the gas for the code deposit. + /// Since we don't have access to the child vm anymore, we charge the gas on + /// the returned ExecutionResult of the childVM. + /// + /// # Arguments + /// * `self` - The ExecutionResult to charge the gas on. + /// * `account` - The Account to finalize + #[inline(always)] + fn finalize_creation( + ref self: ExecutionResult, mut account: Account + ) -> Result { + let code = self.return_data; + let contract_code_gas = code.len().into() * gas::CODEDEPOSIT; + + if code.len() != 0 { + ensure(*code[0] != 0xEF, EVMError::InvalidCode)?; + } + self.charge_gas(contract_code_gas)?; + + ensure(code.len() <= constants::MAX_CODE_SIZE, EVMError::OutOfGas)?; + + account.set_code(code); + Result::Ok(account) + } +} + +#[cfg(test)] +mod tests { + //TODO: test create helpers + + +} diff --git a/cairo/kakarot-ssj/crates/evm/src/errors.cairo b/cairo/kakarot-ssj/crates/evm/src/errors.cairo new file mode 100644 index 000000000..b6d17eaa5 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/errors.cairo @@ -0,0 +1,112 @@ +use core::fmt::{Debug, Formatter, Error, Display}; +use utils::traits::bytes::ToBytes; + +// STACK + +// INSTRUCTIONS +pub const PC_OUT_OF_BOUNDS: felt252 = 'KKT: pc >= bytecode length'; + +// TYPE CONVERSION +pub const TYPE_CONVERSION_ERROR: felt252 = 'KKT: type conversion error'; + +// NUMERIC OPERATIONS +pub const BALANCE_OVERFLOW: felt252 = 'KKT: balance overflow'; + +// JUMP +pub const INVALID_DESTINATION: felt252 = 'KKT: invalid JUMP destination'; + +// CALL +pub const VALUE_TRANSFER_IN_STATIC_CALL: felt252 = 'KKT: transfer value in static'; +pub const ACTIVE_MACHINE_STATE_IN_CALL_FINALIZATION: felt252 = 'KKT: active state in end call'; +pub const MISSING_PARENT_CONTEXT: felt252 = 'KKT: missing parent context'; +pub const CALL_GAS_GT_GAS_LIMIT: felt252 = 'KKT: call gas gt gas limit'; + +// EVM STATE + +// STARKNET_SYSCALLS +pub const READ_SYSCALL_FAILED: felt252 = 'KKT: read syscall failed'; +pub const BLOCK_HASH_SYSCALL_FAILED: felt252 = 'KKT: block_hash syscall failed'; +pub const WRITE_SYSCALL_FAILED: felt252 = 'KKT: write syscall failed'; +pub const CONTRACT_SYSCALL_FAILED: felt252 = 'KKT: contract syscall failed'; +pub const EXECUTION_INFO_SYSCALL_FAILED: felt252 = 'KKT: exec info syscall failed'; + +// CREATE +pub const CONTRACT_ACCOUNT_EXISTS: felt252 = 'KKT: Contract Account exists'; +pub const EOA_EXISTS: felt252 = 'KKT: EOA already exists'; +pub const ACCOUNT_EXISTS: felt252 = 'KKT: Account already exists'; +pub const DEPLOYMENT_FAILED: felt252 = 'KKT: deployment failed'; + +// TRANSACTION ORIGIN +pub const CALLING_FROM_UNDEPLOYED_ACCOUNT: felt252 = 'EOA: from is undeployed EOA'; +pub const CALLING_FROM_CA: felt252 = 'EOA: from is a contract account'; + +#[derive(Drop, Copy, PartialEq)] +pub enum EVMError { + StackOverflow, + StackUnderflow, + TypeConversionError: felt252, + NumericOperations: felt252, + InsufficientBalance, + ReturnDataOutOfBounds, + InvalidJump, + InvalidCode, + NotImplemented, + InvalidParameter: felt252, + InvalidOpcode: u8, + WriteInStaticContext, + Collision, + OutOfGas, + Assertion, + DepthLimit, + MemoryLimitOOG, + NonceOverflow +} + +#[generate_trait] +pub impl EVMErrorImpl of EVMErrorTrait { + fn to_string(self: EVMError) -> felt252 { + match self { + EVMError::StackOverflow => 'stack overflow', + EVMError::StackUnderflow => 'stack underflow', + EVMError::TypeConversionError(error_message) => error_message, + EVMError::NumericOperations(error_message) => error_message, + EVMError::InsufficientBalance => 'insufficient balance', + EVMError::ReturnDataOutOfBounds => 'return data out of bounds', + EVMError::InvalidJump => 'invalid jump destination', + EVMError::InvalidCode => 'invalid code', + EVMError::NotImplemented => 'not implemented', + EVMError::InvalidParameter(error_message) => error_message, + // TODO: refactor with dynamic strings once supported + EVMError::InvalidOpcode => 'invalid opcode'.into(), + EVMError::WriteInStaticContext => 'write protection', + EVMError::Collision => 'create collision'.into(), + EVMError::OutOfGas => 'out of gas'.into(), + EVMError::Assertion => 'assertion failed'.into(), + EVMError::DepthLimit => 'max call depth exceeded'.into(), + EVMError::MemoryLimitOOG => 'memory limit out of gas'.into(), + EVMError::NonceOverflow => 'nonce overflow'.into(), + } + } + + fn to_bytes(self: EVMError) -> Span { + let error_message: felt252 = self.to_string(); + let error_message: u256 = error_message.into(); + error_message.to_be_bytes() + } +} + +pub impl DebugEVMError of Debug { + fn fmt(self: @EVMError, ref f: Formatter) -> Result<(), Error> { + let error_message = (*self).to_string(); + Display::fmt(@error_message, ref f) + } +} + +#[inline(always)] +pub fn ensure(cond: bool, err: EVMError) -> Result<(), EVMError> { + if cond { + Result::Ok(()) + } else { + Result::Err(err) + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/gas.cairo b/cairo/kakarot-ssj/crates/evm/src/gas.cairo new file mode 100644 index 000000000..9454410ae --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/gas.cairo @@ -0,0 +1,403 @@ +use core::cmp::min; +use core::num::traits::CheckedAdd; +use crate::errors::EVMError; +use utils::eth_transaction::common::TxKindTrait; +use utils::eth_transaction::eip2930::{AccessListItem}; +use utils::eth_transaction::transaction::{Transaction, TransactionTrait}; +use utils::helpers::bytes_32_words_size; +use utils::helpers; + +//! Gas costs for EVM operations +//! Code is based on alloy project +//! Source: + +pub const ZERO: u64 = 0; +pub const BASE: u64 = 2; +pub const VERYLOW: u64 = 3; +pub const LOW: u64 = 5; +pub const MID: u64 = 8; +pub const HIGH: u64 = 10; +pub const JUMPDEST: u64 = 1; +pub const SELFDESTRUCT: u64 = 5000; +pub const CREATE: u64 = 32000; +pub const CALLVALUE: u64 = 9000; +pub const NEWACCOUNT: u64 = 25000; +pub const EXP: u64 = 10; +pub const EXP_GAS_PER_BYTE: u64 = 50; +pub const MEMORY: u64 = 3; +pub const LOG: u64 = 375; +pub const LOGDATA: u64 = 8; +pub const LOGTOPIC: u64 = 375; +pub const KECCAK256: u64 = 30; +pub const KECCAK256WORD: u64 = 6; +pub const COPY: u64 = 3; +pub const BLOCKHASH: u64 = 20; +pub const CODEDEPOSIT: u64 = 200; + +pub const SSTORE_SET: u64 = 20000; +pub const SSTORE_RESET: u64 = 5000; +pub const REFUND_SSTORE_CLEARS: u64 = 4800; + +pub const TRANSACTION_ZERO_DATA: u64 = 4; +pub const TRANSACTION_NON_ZERO_DATA_INIT: u64 = 16; +pub const TRANSACTION_NON_ZERO_DATA_FRONTIER: u64 = 68; +pub const TRANSACTION_BASE_COST: u64 = 21000; +pub const TRANSACTION_CREATE_COST: u64 = 32000; + +// Berlin EIP-2929 constants +pub const ACCESS_LIST_ADDRESS: u64 = 2400; +pub const ACCESS_LIST_STORAGE_KEY: u64 = 1900; +pub const COLD_SLOAD_COST: u64 = 2100; +pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600; +pub const WARM_ACCESS_COST: u64 = 100; + +/// EIP-3860 : Limit and meter initcode +pub const INITCODE_WORD_COST: u64 = 2; + +pub const CALL_STIPEND: u64 = 2300; + +// EIP-4844 +pub const BLOB_HASH_COST: u64 = 3; + +/// Defines the gas cost and stipend for executing call opcodes. +/// +/// # Struct fields +/// +/// * `cost`: The non-refundable portion of gas reserved for executing the call opcode. +/// * `stipend`: The portion of gas available to sub-calls that is refundable if not consumed. +#[derive(Drop)] +pub struct MessageCallGas { + pub cost: u64, + pub stipend: u64, +} + +/// Defines the new size and the expansion cost after memory expansion. +/// +/// # Struct fields +/// +/// * `new_size`: The new size of the memory after extension. +/// * `expansion_cost`: The cost of the memory extension. +#[derive(Drop)] +pub struct MemoryExpansion { + pub new_size: u32, + pub expansion_cost: u64, +} + +/// Calculates the maximum gas that is allowed for making a message call. +/// +/// # Arguments +/// * `gas`: The gas available for the message call. +/// +/// # Returns +/// * The maximum gas allowed for the message call. +pub fn max_message_call_gas(gas: u64) -> u64 { + gas - (gas / 64) +} + +/// Calculates the MessageCallGas (cost and stipend) for executing call Opcodes. +/// +/// # Parameters +/// +/// * `value`: The amount of native token that needs to be transferred. +/// * `gas`: The amount of gas provided to the message-call. +/// * `gas_left`: The amount of gas left in the current frame. +/// * `memory_cost`: The amount needed to extend the memory in the current frame. +/// * `extra_gas`: The amount of gas needed for transferring value + creating a new account inside a +/// message call. +/// +/// # Returns +/// +/// * `Result`: The calculated MessageCallGas or an error if overflow +/// occurs. +pub fn calculate_message_call_gas( + value: u256, gas: u64, gas_left: u64, memory_cost: u64, extra_gas: u64 +) -> Result { + let call_stipend = if value == 0 { + 0 + } else { + CALL_STIPEND + }; + + // Check for overflow when adding extra_gas and memory_cost + let total_extra_cost = extra_gas.checked_add(memory_cost).ok_or(EVMError::OutOfGas)?; + let gas = if gas_left < total_extra_cost { + gas + } else { + let remaining_gas = gas_left - total_extra_cost; // Safe because of the check above + min(gas, max_message_call_gas(remaining_gas)) + }; + + let cost = gas.checked_add(extra_gas).ok_or(EVMError::OutOfGas)?; + let stipend = gas.checked_add(call_stipend).ok_or(EVMError::OutOfGas)?; + + Result::Ok(MessageCallGas { cost, stipend }) +} + + +/// Calculates the gas cost for allocating memory +/// to the smallest multiple of 32 bytes, +/// such that the allocated size is at least as big as the given size. +/// +/// To optimize computations on u128 and avoid overflows, we compute size_in_words / 512 +/// instead of size_in_words * size_in_words / 512. Then we recompute the +/// resulting quotient: x^2 = 512q + r becomes +/// x = 512 q0 + r0 => x^2 = 512(512 q0^2 + 2 q0 r0) + r0^2 +/// r0^2 = 512 q1 + r1 +/// x^2 = 512(512 q0^2 + 2 q0 r0 + q1) + r1 +/// q = 512 * q0 * q0 + 2 * q0 * r0 + q1 +/// # Parameters +/// +/// * `size_in_bytes` - The size of the data in bytes. +/// +/// # Returns +/// +/// * `total_gas_cost` - The gas cost for storing data in memory. +pub fn calculate_memory_gas_cost(size_in_bytes: usize) -> u64 { + let _512: NonZero = 512_u64.try_into().unwrap(); + let size_in_words = bytes_32_words_size(size_in_bytes); + let linear_cost = size_in_words.into() * MEMORY; + + let (q0, r0) = DivRem::div_rem(size_in_words.into(), _512); + let (q1, _) = DivRem::div_rem(r0 * r0, _512); + let quadratic_cost = 512 * q0 * q0 + 2 * q0 * r0 + q1; + + linear_cost + quadratic_cost +} + + +/// Calculates memory expansion based on multiple memory operations. +/// +/// # Arguments +/// +/// * `current_size`: Current size of the memory. +/// * `operations`: A span of tuples (offset, size) representing memory operations. +/// +/// # Returns +/// +/// * `MemoryExpansion`: New size and expansion cost. +pub fn memory_expansion( + current_size: usize, mut operations: Span<(usize, usize)> +) -> Result { + let mut current_max_size = current_size; + + // Using a high-level loop because Cairo doesn't support the `for` loop syntax with breaks + let max_size = loop { + match operations.pop_front() { + Option::Some(( + offset, size + )) => { + if *size != 0 { + match (*offset).checked_add(*size) { + Option::Some(end) => { + if end > current_max_size { + current_max_size = end; + } + }, + Option::None => { break Result::Err(EVMError::MemoryLimitOOG); }, + } + } + }, + Option::None => { break Result::Ok((current_max_size)); }, + } + }?; + + let new_size = helpers::bytes_32_words_size(max_size) * 32; + + if new_size <= current_size { + return Result::Ok(MemoryExpansion { new_size: current_size, expansion_cost: 0 }); + } + + let prev_cost = calculate_memory_gas_cost(current_size); + let new_cost = calculate_memory_gas_cost(new_size); + let expansion_cost = new_cost - prev_cost; + Result::Ok(MemoryExpansion { new_size, expansion_cost }) +} + +/// Calculates the gas to be charged for the init code in CREATE/CREATE2 +/// opcodes as well as create transactions. +/// +/// # Arguments +/// +/// * `code_size` - The size of the init code +/// +/// # Returns +/// +/// * `init_code_gas` - The gas to be charged for the init code. +#[inline(always)] +pub fn init_code_cost(code_size: usize) -> u64 { + let code_size_in_words = helpers::bytes_32_words_size(code_size); + code_size_in_words.into() * INITCODE_WORD_COST +} + +/// Calculates the gas that is charged before execution is started. +/// +/// The intrinsic cost of the transaction is charged before execution has +/// begun. Functions/operations in the EVM cost money to execute so this +/// intrinsic cost is for the operations that need to be paid for as part of +/// the transaction. Data transfer, for example, is part of this intrinsic +/// cost. It costs ether to send data over the wire and that ether is +/// accounted for in the intrinsic cost calculated in this function. This +/// intrinsic cost must be calculated and paid for before execution in order +/// for all operations to be implemented. +/// +/// Reference: +/// https://github.com/ethereum/execution-specs/blob/master/src/ethereum/shanghai/fork.py#L689 +pub fn calculate_intrinsic_gas_cost(tx: @Transaction) -> u64 { + let mut data_cost: u64 = 0; + + let mut calldata = tx.input(); + let calldata_len: usize = calldata.len(); + + for data in calldata { + data_cost += + if *data == 0 { + TRANSACTION_ZERO_DATA + } else { + TRANSACTION_NON_ZERO_DATA_INIT + }; + }; + + let create_cost: u64 = if tx.kind().is_create() { + TRANSACTION_CREATE_COST + init_code_cost(calldata_len) + } else { + 0 + }; + + let access_list_cost = if let Option::Some(mut access_list) = tx.access_list() { + let mut access_list_cost: u64 = 0; + for access_list_item in access_list { + let AccessListItem { ethereum_address: _, storage_keys } = *access_list_item; + access_list_cost += ACCESS_LIST_ADDRESS + + (ACCESS_LIST_STORAGE_KEY * storage_keys.len().into()); + }; + access_list_cost + } else { + 0 + }; + + TRANSACTION_BASE_COST + data_cost + create_cost + access_list_cost +} + +#[cfg(test)] +mod tests { + use core::starknet::EthAddress; + + use crate::gas::{ + calculate_intrinsic_gas_cost, calculate_memory_gas_cost, ACCESS_LIST_ADDRESS, + ACCESS_LIST_STORAGE_KEY + }; + use crate::test_utils::evm_address; + use utils::eth_transaction::eip2930::{AccessListItem, TxEip2930}; + use utils::eth_transaction::legacy::TxLegacy; + use utils::eth_transaction::transaction::Transaction; + use utils::traits::bytes::ToBytes; + + #[test] + fn test_calculate_intrinsic_gas_cost() { + // RLP decoded value: (https://toolkit.abdk.consulting/ethereum#rlp,transaction) + // ["0xc9", "0x81", "0xf7", "0x81", "0x80", "0x81", "0x84", "0x00", "0x00", "0x12"] + // 16 16 16 16 16 16 16 4 4 16 + // + 21000 + // + 0 + // --------------------------- + // = 21136 + let rlp_encoded: u256 = 0xc981f781808184000012; + + let input = rlp_encoded.to_be_bytes(); + let to: EthAddress = 'vitalik.eth'.try_into().unwrap(); + + let tx: Transaction = Transaction::Legacy( + TxLegacy { + to: to.into(), + nonce: 0, + gas_price: 50, + gas_limit: 433926, + value: 1, + input, + chain_id: Option::Some(0x1) + } + ); + + let expected_cost: u64 = 21136; + let out_cost: u64 = calculate_intrinsic_gas_cost(@tx); + + assert_eq!(out_cost, expected_cost, "wrong cost"); + } + + #[test] + fn test_calculate_intrinsic_gas_cost_with_access_list() { + // RLP decoded value: (https://toolkit.abdk.consulting/ethereum#rlp,transaction) + // ["0xc9", "0x81", "0xf7", "0x81", "0x80", "0x81", "0x84", "0x00", "0x00", "0x12"] + // 16 16 16 16 16 16 16 4 4 16 + // + 21000 + // + 0 + // --------------------------- + // = 21136 + let rlp_encoded: u256 = 0xc981f781808184000012; + + let input = rlp_encoded.to_be_bytes(); + let to: EthAddress = 'vitalik.eth'.try_into().unwrap(); + + let access_list = [ + AccessListItem { ethereum_address: evm_address(), storage_keys: [1, 2, 3, 4, 5].span() } + ].span(); + + let tx: Transaction = Transaction::Eip2930( + TxEip2930 { + to: to.into(), + nonce: 0, + gas_price: 50, + gas_limit: 433926, + value: 1, + input, + chain_id: 0x1, + access_list + } + ); + + let expected_cost: u64 = 21136 + ACCESS_LIST_ADDRESS + 5 * ACCESS_LIST_STORAGE_KEY; + let out_cost: u64 = calculate_intrinsic_gas_cost(@tx); + + assert_eq!(out_cost, expected_cost, "wrong cost"); + } + + + #[test] + fn test_calculate_intrinsic_gas_cost_without_destination() { + // RLP decoded value: (https://toolkit.abdk.consulting/ethereum#rlp,transaction) + // ["0xc9", "0x81", "0xf7", "0x81", "0x80", "0x81", "0x84", "0x00", "0x00", "0x12"] + // 16 16 16 16 16 16 16 4 4 16 + // + 21000 + // + (32000 + 2) + // --------------------------- + // = 53138 + let rlp_encoded: u256 = 0xc981f781808184000012; + + let input = rlp_encoded.to_be_bytes(); + + let tx: Transaction = Transaction::Legacy( + TxLegacy { + to: Option::None.into(), + nonce: 0, + gas_price: 50, + gas_limit: 433926, + value: 1, + input, + chain_id: Option::Some(0x1) + } + ); + + let expected_cost: u64 = 53138; + let out_cost: u64 = calculate_intrinsic_gas_cost(@tx); + + assert_eq!(out_cost, expected_cost, "wrong cost"); + } + + #[test] + fn test_calculate_memory_allocation_cost() { + let size_in_bytes: usize = 10018613; + let expected_cost: u64 = 192385220; + let out_cost: u64 = calculate_memory_gas_cost(size_in_bytes); + assert_eq!(out_cost, expected_cost, "wrong cost"); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions.cairo new file mode 100644 index 000000000..e632d1169 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions.cairo @@ -0,0 +1,34 @@ +/// Sub modules. +mod block_information; + +mod comparison_operations; + +mod duplication_operations; + +mod environmental_information; + +mod exchange_operations; + +mod logging_operations; + +mod memory_operations; + +mod push_operations; + +mod sha3; + +mod stop_and_arithmetic_operations; + +mod system_operations; + +pub use block_information::BlockInformationTrait; +pub use comparison_operations::ComparisonAndBitwiseOperationsTrait; +pub use duplication_operations::DuplicationOperationsTrait; +pub use environmental_information::EnvironmentInformationTrait; +pub use exchange_operations::ExchangeOperationsTrait; +pub use logging_operations::LoggingOperationsTrait; +pub use memory_operations::MemoryOperationTrait; +pub use push_operations::PushOperationsTrait; +pub use sha3::Sha3Trait; +pub use stop_and_arithmetic_operations::StopAndArithmeticOperationsTrait; +pub use system_operations::SystemOperationsTrait; diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/block_information.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/block_information.cairo new file mode 100644 index 000000000..3f834a917 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/block_information.cairo @@ -0,0 +1,372 @@ +//! Block Information. + +use core::num::traits::SaturatingAdd; +use core::starknet::SyscallResultTrait; +use core::starknet::syscalls::get_block_hash_syscall; + +use crate::errors::EVMError; + +use crate::gas; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; +use crate::state::StateTrait; +use utils::constants::MIN_BASE_FEE_PER_BLOB_GAS; +use utils::traits::{EthAddressTryIntoResultContractAddress, EthAddressIntoU256}; + +#[generate_trait] +pub impl BlockInformation of BlockInformationTrait { + /// 0x40 - BLOCKHASH + /// Get the hash of one of the 256 most recent complete blocks. + /// # Specification: https://www.evm.codes/#40?fork=shanghai + fn exec_blockhash(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BLOCKHASH)?; + + // Saturate to MAX_U64 to avoid a revert when the hash requested is too big. It should just + // push 0. + let block_number = self.stack.pop_saturating_u64()?; + let current_block = self.env.block_number; + + // If input block number is lower than current_block - 256, return 0 + // If input block number is higher than current_block - 10, return 0 + // Note: in the specs, input block number can be equal - at most - to the current block + // number minus one. + // In Starknet, the `get_block_hash_syscall` is capped at current block minus ten. + // TODO: monitor the changes in the `get_block_hash_syscall` syscall. + // source: + // https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-calls-cairo1/#get_block_hash + if block_number.saturating_add(10) > current_block + || block_number.saturating_add(256) < current_block { + return self.stack.push(0); + } + + self.stack.push(get_block_hash_syscall(block_number).unwrap_syscall().into()) + } + + /// 0x41 - COINBASE + /// Get the block's beneficiary address. + /// # Specification: https://www.evm.codes/#41?fork=shanghai + fn exec_coinbase(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + let coinbase = self.env.coinbase; + self.stack.push(coinbase.into()) + } + + /// 0x42 - TIMESTAMP + /// Get the block’s timestamp + /// # Specification: https://www.evm.codes/#42?fork=shanghai + fn exec_timestamp(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + self.stack.push(self.env.block_timestamp.into()) + } + + /// 0x43 - NUMBER + /// Get the block number. + /// # Specification: https://www.evm.codes/#43?fork=shanghai + fn exec_number(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + self.stack.push(self.env.block_number.into()) + } + + /// 0x44 - PREVRANDAO + /// # Specification: https://www.evm.codes/#44?fork=shanghai + fn exec_prevrandao(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + self.stack.push(self.env.prevrandao) + } + + /// 0x45 - GASLIMIT + /// Get the block’s gas limit + /// # Specification: https://www.evm.codes/#45?fork=shanghai + fn exec_gaslimit(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + self.stack.push(self.env.block_gas_limit.into()) + } + + /// 0x46 - CHAINID + /// Get the chain ID. + /// # Specification: https://www.evm.codes/#46?fork=shanghai + fn exec_chainid(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + let chain_id = self.env.chain_id; + self.stack.push(chain_id.into()) + } + + /// 0x47 - SELFBALANCE + /// Get balance of currently executing contract + /// # Specification: https://www.evm.codes/#47?fork=shanghai + fn exec_selfbalance(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::LOW)?; + + let evm_address = self.message().target.evm; + + let balance = self.env.state.get_account(evm_address).balance; + + self.stack.push(balance) + } + + /// 0x48 - BASEFEE + /// Get base fee. + /// # Specification: https://www.evm.codes/#48?fork=shanghai + fn exec_basefee(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + self.stack.push(self.env.base_fee.into()) + } + + /// 0x49 - BLOBHASH + /// Returns the value of the blob hash of the current block + /// Always returns Zero in the context of Kakarot + /// # Specification: https://www.evm.codes/#49?fork=cancun + fn exec_blobhash(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BLOB_HASH_COST)?; + + self.stack.push(0) + } + + /// 0x4A - BLOBBASEFEE + /// Returns the value of the blob base-fee of the current block + /// Always returns Zero in the context of Kakarot + /// # Specification: https://www.evm.codes/#4a?fork=cancun + fn exec_blobbasefee(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + self.stack.push(MIN_BASE_FEE_PER_BLOB_GAS.into()) + } +} + + +#[cfg(test)] +mod tests { + use core::result::ResultTrait; + use crate::instructions::BlockInformationTrait; + use crate::model::account::Account; + use crate::model::vm::VMTrait; + use crate::stack::StackTrait; + use crate::state::StateTrait; + use crate::test_utils::{VMBuilderTrait, gas_price, setup_test_environment}; + use snforge_std::{start_cheat_block_number_global, start_cheat_block_timestamp_global}; + use utils::constants::EMPTY_KECCAK; + use utils::constants; + use utils::traits::{EthAddressIntoU256}; + + + /// 0x40 - BLOCKHASH + #[test] + fn test_exec_blockhash_below_bounds() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + start_cheat_block_number_global(500); + + // When + vm.stack.push(243).expect('push failed'); + vm.exec_blockhash().unwrap(); + + // Then + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + #[test] + fn test_exec_blockhash_above_bounds() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + start_cheat_block_number_global(500); + + // When + vm.stack.push(491).expect('push failed'); + vm.exec_blockhash().unwrap(); + + // Then + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + // TODO: implement exec_blockhash testing for block number within bounds + //TODO(sn-foundry): mock the block hash + // https://github.com/starkware-libs/cairo/blob/77a7e7bc36aa1c317bb8dd5f6f7a7e6eef0ab4f3/crates/cairo-lang-starknet/cairo_level_tests/interoperability.cairo#L173 + #[test] + #[ignore] + fn test_exec_blockhash_within_bounds() { + // If not set the default block number is 0. + let queried_block = 244; + start_cheat_block_number_global(500); + //TODO: restore start_cheat_block_hash_global(queried_block, 0xF); + + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.stack.push(queried_block.into()).expect('push failed'); + vm.exec_blockhash().expect('exec failed'); + //TODO the CASM runner used in tests doesn't implement + //`get_block_hash_syscall` yet. As such, this test should fail no if the + //queried block is within bounds + // Then + assert(vm.stack.peek().unwrap() == 0xF, 'stack top should be 0xF'); + } + + + #[test] + fn test_block_timestamp_set_to_1692873993() { + // 24/08/2023 12h46 33s + // If not set the default timestamp is 0. + start_cheat_block_timestamp_global(1692873993); + + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_timestamp().unwrap(); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 1692873993, 'stack top should be 1692873993'); + } + + #[test] + fn test_block_number_set_to_32() { + // If not set the default block number is 0. + start_cheat_block_number_global(32); + + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_number().unwrap(); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 32, 'stack top should be 32'); + } + + #[test] + fn test_gaslimit() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_gaslimit().unwrap(); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + // This value is set in [new_with_presets]. + assert_eq!(vm.stack.peek().unwrap(), constants::BLOCK_GAS_LIMIT.into()) + } + + // ************************************************************************* + // 0x47: SELFBALANCE + // ************************************************************************* + #[test] + fn test_exec_selfbalance_should_push_balance() { + // Given + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 400, + nonce: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + + // When + vm.exec_selfbalance().unwrap(); + + // Then + assert_eq!(vm.stack.peek().unwrap(), 400); + } + + + #[test] + fn test_basefee_should_push_env_base_fee() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_basefee().unwrap(); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), vm.env.base_fee.into()); + } + + #[test] + fn test_chainid_should_push_chain_id_to_stack() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_chainid().unwrap(); + + // Then + let chain_id = vm.stack.peek().unwrap(); + assert(vm.env.chain_id.into() == chain_id, 'stack should have chain id'); + } + + + #[test] + fn test_randao_should_push_zero_to_stack() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_prevrandao().unwrap(); + + // Then + let result = vm.stack.peek().unwrap(); + assert(result == 0x00, 'stack top should be zero'); + } + + #[test] + fn test_blobhash_should_return_zero() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_blobhash().unwrap(); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0); + } + + + #[test] + fn test_blobbasefee_should_return_one() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_blobbasefee().unwrap(); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 1); + } + + + // ************************************************************************* + // 0x41: COINBASE + // ************************************************************************* + #[test] + fn test_exec_coinbase() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_coinbase().unwrap(); + + // Then + let coinbase_address = vm.stack.peek().unwrap(); + assert(vm.env.coinbase.into() == coinbase_address, 'wrong coinbase address'); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/comparison_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/comparison_operations.cairo new file mode 100644 index 000000000..b040f21b0 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/comparison_operations.cairo @@ -0,0 +1,1987 @@ +use core::num::traits::Bounded; +use crate::errors::EVMError; +use crate::gas; +use crate::model::vm::{VM, VMTrait}; +// Internal imports +use crate::stack::StackTrait; +use utils::constants::{POW_2_127}; +use utils::i256::i256; +use utils::math::{Bitshift, WrappingBitshift}; +use utils::traits::BoolIntoNumeric; + +#[generate_trait] +pub impl ComparisonAndBitwiseOperations of ComparisonAndBitwiseOperationsTrait { + /// 0x10 - LT + /// # Specification: https://www.evm.codes/#10?fork=shanghai + fn exec_lt(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let a = *popped[0]; + let b = *popped[1]; + let result = (a < b).into(); + self.stack.push(result) + } + + /// 0x11 - GT + /// # Specification: https://www.evm.codes/#11?fork=shanghai + fn exec_gt(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let a = *popped[0]; + let b = *popped[1]; + let result = (a > b).into(); + self.stack.push(result) + } + + + /// 0x12 - SLT + /// # Specification: https://www.evm.codes/#12?fork=shanghai + fn exec_slt(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let a: i256 = self.stack.pop_i256()?; + let b: i256 = self.stack.pop_i256()?; + let result: u256 = (a < b).into(); + self.stack.push(result) + } + + /// 0x13 - SGT + /// # Specification: https://www.evm.codes/#13?fork=shanghai + fn exec_sgt(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let a: i256 = self.stack.pop_i256()?; + let b: i256 = self.stack.pop_i256()?; + let result: u256 = (a > b).into(); + self.stack.push(result) + } + + + /// 0x14 - EQ + /// # Specification: https://www.evm.codes/#14?fork=shanghai + fn exec_eq(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let a = *popped[0]; + let b = *popped[1]; + let result = (a == b).into(); + self.stack.push(result) + } + + /// 0x15 - ISZERO + /// # Specification: https://www.evm.codes/#15?fork=shanghai + fn exec_iszero(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop()?; + let result: u256 = (popped == 0).into(); + self.stack.push(result) + } + + /// 0x16 - AND + /// # Specification: https://www.evm.codes/#16?fork=shanghai + fn exec_and(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let a = *popped[0]; + let b = *popped[1]; + let result = a & b; + self.stack.push(result) + } + + /// 0x17 - OR + /// # Specification: https://www.evm.codes/#17?fork=shanghai + fn exec_or(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let a = *popped[0]; + let b = *popped[1]; + let result = a | b; + self.stack.push(result) + } + + /// 0x18 - XOR operation + /// # Specification: https://www.evm.codes/#18?fork=shanghai + fn exec_xor(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let a = *popped[0]; + let b = *popped[1]; + let result = a ^ b; + self.stack.push(result) + } + + /// 0x19 - NOT + /// Bitwise NOT operation + /// # Specification: https://www.evm.codes/#19?fork=shanghai + fn exec_not(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let a = self.stack.pop()?; + let result = ~a; + self.stack.push(result) + } + + /// 0x1A - BYTE + /// # Specification: https://www.evm.codes/#1a?fork=shanghai + /// Retrieve single byte located at the byte offset of value, starting from the most significant + /// byte. + fn exec_byte(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let i = *popped[0]; + let x = *popped[1]; + + /// If the byte offset is out of range, we early return with 0. + if i > 31 { + return self.stack.push(0); + } + let i: usize = i.try_into().unwrap(); // Safe because i <= 31 + + // Right shift value by offset bits and then take the least significant byte. + let result = x.shr((31 - i) * 8) & 0xFF; + self.stack.push(result) + } + + /// 0x1B - SHL + /// # Specification: https://www.evm.codes/#1b?fork=shanghai + fn exec_shl(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let shift = *popped[0]; + let val = *popped[1]; + + // if shift is bigger than 255 return 0 + if shift > 255 { + return self.stack.push(0); + } + let shift: usize = shift.try_into().unwrap(); // Safe because shift <= 255 + let result = val.wrapping_shl(shift); + self.stack.push(result) + } + + /// 0x1C - SHR + /// # Specification: https://www.evm.codes/#1c?fork=shanghai + fn exec_shr(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + let shift = *popped[0]; + let value = *popped[1]; + + // if shift is bigger than 255 return 0 + if shift > 255 { + return self.stack.push(0); + } + let shift: usize = shift.try_into().unwrap(); // Safe because shift <= 255 + let result = value.wrapping_shr(shift); + self.stack.push(result) + } + + /// 0x1D - SAR + /// # Specification: https://www.evm.codes/#1d?fork=shanghai + fn exec_sar(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let shift: u256 = self.stack.pop()?; + let value: i256 = self.stack.pop_i256()?; + + // Checks the MSB bit sign for a 256-bit integer + let positive = value.value.high < POW_2_127; + let sign = if positive { + // If sign is positive, set it to 0. + 0 + } else { + // If sign is negative, set the number to -1. + Bounded::::MAX + }; + + if (shift >= 256) { + self.stack.push(sign) + } else { + let shift: usize = shift.try_into().unwrap(); // Safe because shift <= 256 + // XORing with sign before and after the shift propagates the sign bit of the operation + let result = (sign ^ value.value).shr(shift) ^ sign; + self.stack.push(result) + } + } +} + + +#[cfg(test)] +mod tests { + use core::num::traits::Bounded; + use crate::instructions::ComparisonAndBitwiseOperationsTrait; + use crate::stack::StackTrait; + use crate::test_utils::VMBuilderTrait; + + #[test] + fn test_eq_same_pair() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xFEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210) + .expect('push failed'); + vm + .stack + .push(0xFEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210) + .expect('push failed'); + + // When + vm.exec_eq().expect('exec_eq failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x01, 'stack top should be 0x01'); + } + + #[test] + fn test_eq_different_pair() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xAB8765432DCBA98765410F149E87610FDCBA98765432543217654DCBA93210F8) + .expect('push failed'); + vm + .stack + .push(0xFEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210) + .expect('push failed'); + + // When + vm.exec_eq().expect('exec_eq failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be 0x00'); + } + + #[test] + fn test_and_zero_and_max() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(Bounded::::MAX).unwrap(); + + // When + vm.exec_and().expect('exec_and failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be 0x00'); + } + + #[test] + fn test_and_max_and_max() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(Bounded::::MAX).unwrap(); + vm.stack.push(Bounded::::MAX).unwrap(); + + // When + vm.exec_and().expect('exec_and failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == Bounded::::MAX, 'stack top should be 0xFF...FFF'); + } + + #[test] + fn test_and_two_random_uint() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xAB8765432DCBA98765410F149E87610FDCBA98765432543217654DCBA93210F8) + .expect('push failed'); + vm + .stack + .push(0xFEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210) + .expect('push failed'); + + // When + vm.exec_and().expect('exec_and failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm + .stack + .peek() + .unwrap() == 0xAA8420002440200064400A1016042000DC989810541010101644088820101010, + 'stack top is wrong' + ); + } + + + #[test] + fn test_xor_different_pair() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0b010101).expect('push failed'); + vm.stack.push(0b101010).expect('push failed'); + + // When + vm.exec_xor().expect('exec_xor failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0b111111, 'stack top should be 0xFF'); + } + + #[test] + fn test_xor_same_pair() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0b000111).expect('push failed'); + vm.stack.push(0b000111).expect('push failed'); + + // When + vm.exec_xor().expect('exec_xor failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be 0x00'); + } + + #[test] + fn test_xor_half_same_pair() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0b111000).expect('push failed'); + vm.stack.push(0b000000).expect('push failed'); + + // When + vm.exec_xor().expect('exec_xor failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0b111000, 'stack top should be 0xFF'); + } + + + #[test] + fn test_not_zero() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x00).expect('push failed'); + + // When + vm.exec_not().expect('exec_not failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == Bounded::::MAX, 'stack top should be 0xFFF..FFFF'); + } + + #[test] + fn test_not_max_uint() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(Bounded::::MAX).unwrap(); + + // When + vm.exec_not().expect('exec_not failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be 0x00'); + } + + #[test] + fn test_not_random_uint() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0x123456789ABCDEF123456789ABCDEF123456789ABCDEF123456789ABCDEF1234) + .expect('push failed'); + + // When + vm.exec_not().expect('exec_not failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm + .stack + .peek() + .unwrap() == 0xEDCBA9876543210EDCBA9876543210EDCBA9876543210EDCBA9876543210EDCB, + 'stack top should be 0x7553' + ); + } + + #[test] + fn test_is_zero_true() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x00).expect('push failed'); + + // When + vm.exec_iszero().expect('exec_iszero failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x01, 'stack top should be true'); + } + + #[test] + fn test_is_zero_false() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x01).expect('push failed'); + + // When + vm.exec_iszero().expect('exec_iszero failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be false'); + } + + #[test] + fn test_byte_random_u256() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xf7ec8b2ea4a6b7fd5f4ed41b66197fcc14c4a37d68275ea151d899bb4d7c2ae7) + .expect('push failed'); + vm.stack.push(0x08).expect('push failed'); + + // When + vm.exec_byte().expect('exec_byte failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x5f, 'stack top should be 0x22'); + } + + #[test] + fn test_byte_offset_out_of_range() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0x01be893aefcfa1592f60622b80d45c2db74281d2b9e10c14b0f6ce7c8f58e209) + .expect('push failed'); + vm.stack.push(32_u256).expect('push failed'); + + // When + vm.exec_byte().expect('exec_byte failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be 0x00'); + } + + #[test] + fn test_exec_gt_true() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(9_u256).expect('push failed'); + vm.stack.push(10_u256).expect('push failed'); + + // When + vm.exec_gt().expect('exec_gt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 1, 'stack top should be 1'); + } + + #[test] + fn test_exec_shl() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xff00000000000000000000000000000000000000000000000000000000000000) + .expect('push failed'); + vm.stack.push(4_u256).expect('push failed'); + + // When + vm.exec_shl().expect('exec_shl failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm + .stack + .peek() + .unwrap() == 0xf000000000000000000000000000000000000000000000000000000000000000, + 'stack top should be 0xf00000...' + ); + } + + #[test] + fn test_exec_shl_wrapping() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xff00000000000000000000000000000000000000000000000000000000000000) + .expect('push failed'); + vm.stack.push(256_u256).expect('push failed'); + + // When + vm.exec_shl().expect('exec_shl failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'if shift > 255 should return 0'); + } + + #[test] + fn test_exec_gt_false() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(10_u256).expect('push failed'); + vm.stack.push(9_u256).expect('push failed'); + + // When + vm.exec_gt().expect('exec_gt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + #[test] + fn test_exec_gt_false_equal() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(10_u256).expect('push failed'); + vm.stack.push(10_u256).expect('push failed'); + + // When + vm.exec_gt().expect('exec_gt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + #[test] + fn test_exec_slt() { + // https://github.com/ethereum/go-ethereum/blob/master/core/vm/testdata/testcases_slt.json + assert_slt(0x0, 0x0, 0); + assert_slt(0x0, 0x1, 0); + assert_slt(0x0, 0x5, 0); + assert_slt(0x0, 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0); + assert_slt(0x0, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0); + assert_slt(0x0, 0x8000000000000000000000000000000000000000000000000000000000000000, 1); + assert_slt(0x0, 0x8000000000000000000000000000000000000000000000000000000000000001, 1); + assert_slt(0x0, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 1); + assert_slt(0x0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1); + assert_slt(0x1, 0x0, 1); + assert_slt(0x1, 0x1, 0); + assert_slt(0x1, 0x5, 0); + assert_slt(0x1, 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0); + assert_slt(0x1, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0); + assert_slt(0x0, 0x8000000000000000000000000000000000000000000000000000000000000000, 1); + assert_slt(0x1, 0x8000000000000000000000000000000000000000000000000000000000000001, 1); + assert_slt(0x1, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 1); + assert_slt(0x1, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1); + assert_slt(0x5, 0x0, 1); + assert_slt(0x5, 0x1, 1); + assert_slt(0x5, 0x5, 0); + assert_slt(0x5, 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0); + assert_slt(0x5, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0); + assert_slt(0x5, 0x8000000000000000000000000000000000000000000000000000000000000000, 1); + assert_slt(0x5, 0x8000000000000000000000000000000000000000000000000000000000000001, 1); + assert_slt(0x5, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 1); + assert_slt(0x5, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1); + assert_slt(0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0x0, 1); + assert_slt(0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0x1, 1); + assert_slt(0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0x5, 1); + assert_slt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0 + ); + assert_slt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 1 + ); + assert_slt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 1 + ); + assert_slt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 1 + ); + assert_slt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_slt(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x0, 1); + assert_slt(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1, 1); + assert_slt(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x5, 1); + assert_slt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 1 + ); + assert_slt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 1 + ); + assert_slt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 1 + ); + assert_slt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 1 + ); + assert_slt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_slt(0x8000000000000000000000000000000000000000000000000000000000000000, 0x0, 0); + assert_slt(0x8000000000000000000000000000000000000000000000000000000000000000, 0x1, 0); + assert_slt(0x8000000000000000000000000000000000000000000000000000000000000000, 0x5, 0); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt(0x8000000000000000000000000000000000000000000000000000000000000001, 0x0, 0); + assert_slt(0x8000000000000000000000000000000000000000000000000000000000000001, 0x1, 0); + assert_slt(0x8000000000000000000000000000000000000000000000000000000000000001, 0x5, 0); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 1 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0 + ); + assert_slt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0x0, 0); + assert_slt(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0x1, 0); + assert_slt(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0x5, 0); + assert_slt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0 + ); + assert_slt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 1 + ); + assert_slt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 1 + ); + assert_slt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0 + ); + assert_slt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x0, 0); + assert_slt(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1, 0); + assert_slt(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x5, 0); + assert_slt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0 + ); + assert_slt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_slt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 1 + ); + assert_slt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 1 + ); + assert_slt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 1 + ); + assert_slt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + } + + fn assert_slt(b: u256, a: u256, expected: u256) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(b).expect('push failed'); + vm.stack.push(a).expect('push failed'); + + // When + vm.exec_slt().expect('exec_slt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == expected, 'slt failed'); + } + + #[test] + fn test_exec_sgt() { + // https://github.com/ethereum/go-ethereum/blob/master/core/vm/testdata/testcases_sgt.json + assert_sgt(0x0, 0x0, 0); + assert_sgt(0x0, 0x1, 1); + assert_sgt(0x0, 0x5, 1); + assert_sgt(0x0, 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 1); + assert_sgt(0x0, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1); + assert_sgt(0x0, 0x8000000000000000000000000000000000000000000000000000000000000000, 0); + assert_sgt(0x0, 0x8000000000000000000000000000000000000000000000000000000000000001, 0); + assert_sgt(0x0, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0); + assert_sgt(0x0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0); + assert_sgt(0x1, 0x0, 0); + assert_sgt(0x1, 0x1, 0); + assert_sgt(0x1, 0x5, 1); + assert_sgt(0x1, 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 1); + assert_sgt(0x1, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1); + assert_sgt(0x1, 0x8000000000000000000000000000000000000000000000000000000000000000, 0); + assert_sgt(0x1, 0x8000000000000000000000000000000000000000000000000000000000000001, 0); + assert_sgt(0x1, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0); + assert_sgt(0x1, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0); + assert_sgt(0x5, 0x0, 0); + assert_sgt(0x5, 0x1, 0); + assert_sgt(0x5, 0x5, 0); + assert_sgt(0x5, 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 1); + assert_sgt(0x5, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 1); + assert_sgt(0x5, 0x8000000000000000000000000000000000000000000000000000000000000000, 0); + assert_sgt(0x5, 0x8000000000000000000000000000000000000000000000000000000000000001, 0); + assert_sgt(0x5, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0); + assert_sgt(0x5, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0); + assert_sgt(0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0x0, 0); + assert_sgt(0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0x1, 0); + assert_sgt(0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, 0x5, 0); + assert_sgt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0 + ); + assert_sgt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0 + ); + assert_sgt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0 + ); + assert_sgt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0 + ); + assert_sgt( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_sgt(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x0, 0); + assert_sgt(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1, 0); + assert_sgt(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x5, 0); + assert_sgt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0 + ); + assert_sgt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_sgt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0 + ); + assert_sgt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0 + ); + assert_sgt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0 + ); + assert_sgt( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + assert_sgt(0x8000000000000000000000000000000000000000000000000000000000000000, 0x0, 1); + assert_sgt(0x8000000000000000000000000000000000000000000000000000000000000000, 0x1, 1); + assert_sgt(0x8000000000000000000000000000000000000000000000000000000000000000, 0x5, 1); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 1 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 1 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 1 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt(0x8000000000000000000000000000000000000000000000000000000000000001, 0x0, 1); + assert_sgt(0x8000000000000000000000000000000000000000000000000000000000000001, 0x1, 1); + assert_sgt(0x8000000000000000000000000000000000000000000000000000000000000001, 0x5, 1); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 1 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 1 + ); + assert_sgt( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0x0, 1); + assert_sgt(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0x1, 1); + assert_sgt(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, 0x5, 1); + assert_sgt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 1 + ); + assert_sgt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0 + ); + assert_sgt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0 + ); + assert_sgt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0 + ); + assert_sgt( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x0, 1); + assert_sgt(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x1, 1); + assert_sgt(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x5, 1); + assert_sgt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 1 + ); + assert_sgt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 1 + ); + assert_sgt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0 + ); + assert_sgt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0 + ); + assert_sgt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0 + ); + assert_sgt( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0 + ); + } + + fn assert_sgt(b: u256, a: u256, expected: u256) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(b).expect('push failed'); + vm.stack.push(a).expect('push failed'); + + // When + vm.exec_sgt().expect('exec_sgt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == expected, 'sgt failed'); + } + + #[test] + fn test_exec_shr() { + // https://github.com/ethereum/go-ethereum/blob/master/core/vm/testdata/testcases_shr.json + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000001 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000005 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000002 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x4000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0400000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x4000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0400000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_shr( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + } + + fn assert_shr(a: u256, b: u256, expected: u256) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(a).expect('push failed'); + vm.stack.push(b).expect('push failed'); + + // When + vm.exec_shr().expect('exec_shr failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == expected, 'shr failed'); + } + + #[test] + fn test_exec_sar() { + // https://github.com/ethereum/go-ethereum/blob/master/core/vm/testdata/testcases_sar.json + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000001 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000005 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000002 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xc000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xfc00000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000001 + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xc000000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xfc00000000000000000000000000000000000000000000000000000000000000 + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000000, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0x8000000000000000000000000000000000000000000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + assert_sar( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + } + + fn assert_sar(a: u256, b: u256, expected: u256) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(a).expect('push failed'); + vm.stack.push(b).expect('push failed'); + + // When + + vm.exec_sar().expect('exec_sar failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == expected, 'sar failed'); + } + + #[test] + fn test_exec_or_should_pop_0_and_1_and_push_0xCD_when_0_is_0x89_and_1_is_0xC5() { + //Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x89).expect('push failed'); + vm.stack.push(0xC5).expect('push failed'); + + //When + vm.exec_or().expect('exec_or failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0xCD, 'stack top should be 0xCD'); + } + + #[test] + fn test_or_true() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x01).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + vm.exec_or().expect('exec_or failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x01, 'stack top should be 0x01'); + } + + #[test] + fn test_or_false() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + vm.exec_or().expect('exec_or failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be 0x00'); + } + + + #[test] + fn test_exec_lt_true() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(10_u256).expect('push failed'); + vm.stack.push(9_u256).expect('push failed'); + + // When + vm.exec_lt().expect('exec_lt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x01, 'stack top should be true'); + } + + #[test] + fn test_exec_lt_false() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(10_u256).expect('push failed'); + vm.stack.push(20_u256).expect('push failed'); + + // When + vm.exec_lt().expect('exec_lt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be false'); + } + + #[test] + fn test_exec_lt_false_eq() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(10_u256).expect('push failed'); + vm.stack.push(10_u256).expect('push failed'); + + // When + vm.exec_lt().expect('exec_lt failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x00, 'stack top should be false'); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/duplication_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/duplication_operations.cairo new file mode 100644 index 000000000..a69796598 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/duplication_operations.cairo @@ -0,0 +1,557 @@ +//! Duplication Operations. + +// Internal imports +use crate::errors::EVMError; +use crate::gas; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; + +/// Generic DUP operation +#[inline(always)] +fn exec_dup_i(ref self: VM, i: NonZero) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let i: u8 = i.into(); + let item = self.stack.peek_at((i - 1).into())?; + self.stack.push(item) +} + +#[generate_trait] +pub impl DuplicationOperations of DuplicationOperationsTrait { + /// 0x80 - DUP1 operation + #[inline(always)] + fn exec_dup1(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 1) + } + + /// 0x81 - DUP2 operation + #[inline(always)] + fn exec_dup2(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 2) + } + + /// 0x82 - DUP3 operation + #[inline(always)] + fn exec_dup3(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 3) + } + + /// 0x83 - DUP2 operation + #[inline(always)] + fn exec_dup4(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 4) + } + + /// 0x84 - DUP5 operation + #[inline(always)] + fn exec_dup5(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 5) + } + + /// 0x85 - DUP6 operation + #[inline(always)] + fn exec_dup6(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 6) + } + + /// 0x86 - DUP7 operation + #[inline(always)] + fn exec_dup7(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 7) + } + + /// 0x87 - DUP8 operation + #[inline(always)] + fn exec_dup8(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 8) + } + + /// 0x88 - DUP9 operation + #[inline(always)] + fn exec_dup9(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 9) + } + + /// 0x89 - DUP10 operation + #[inline(always)] + fn exec_dup10(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 10) + } + + /// 0x8A - DUP11 operation + #[inline(always)] + fn exec_dup11(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 11) + } + + /// 0x8B - DUP12 operation + #[inline(always)] + fn exec_dup12(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 12) + } + + /// 0x8C - DUP13 operation + #[inline(always)] + fn exec_dup13(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 13) + } + + /// 0x8D - DUP14 operation + #[inline(always)] + fn exec_dup14(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 14) + } + + /// 0x8E - DUP15 operation + #[inline(always)] + fn exec_dup15(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 15) + } + + /// 0x8F - DUP16 operation + #[inline(always)] + fn exec_dup16(ref self: VM) -> Result<(), EVMError> { + exec_dup_i(ref self, 16) + } +} + +#[cfg(test)] +mod tests { + use crate::instructions::DuplicationOperationsTrait; + use crate::stack::Stack; + use crate::stack::StackTrait; + use crate::test_utils::VMBuilderTrait; + + + // ensures all values start from index `from` upto index `to` of stack are `0x0` + fn ensures_zeros(ref stack: Stack, from: u32, to: u32) { + if to > from { + return; + } + + for idx in from..to { + assert(stack.peek_at(idx).unwrap() == 0x00, 'should be zero'); + }; + } + + // push `n` number of `0x0` to the stack + fn push_zeros(ref stack: Stack, n: u8) { + for _ in 0..n { + stack.push(0x0).unwrap(); + }; + } + + #[test] + fn test_dup1() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup1().expect('exec_dup1 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup2() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 1); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup2().expect('exec_dup2 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup3() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 2); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup3().expect('exec_dup3 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup4() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 3); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup4().expect('exec_dup4 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup5() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 4); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup5().expect('exec_dup5 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup6() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 5); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup6().expect('exec_dup6 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup7() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 6); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup7().expect('exec_dup7 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup8() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 7); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup8().expect('exec_dup8 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup9() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 8); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup9().expect('exec_dup9 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup10() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 9); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup10().expect('exec_dup10 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup11() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 10); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup11().expect('exec_dup11 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup12() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 11); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup12().expect('exec_dup12 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup13() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 12); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup13().expect('exec_dup13 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup14() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 13); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup14().expect('exec_dup14 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup15() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 14); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup15().expect('exec_dup15 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } + + #[test] + fn test_dup16() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let initial_len = vm.stack.len(); + + vm.stack.push(0x01).expect('push failed'); + push_zeros(ref vm.stack, 15); + + let old_stack_len = vm.stack.len(); + + // When + vm.exec_dup16().expect('exec_dup16 failed'); + + // Then + let new_stack_len = vm.stack.len(); + + assert(new_stack_len == old_stack_len + 1, 'len should increase by 1'); + + assert(vm.stack.peek_at(initial_len).unwrap() == 0x01, 'first inserted spot should be 1'); + assert(vm.stack.peek_at(new_stack_len - 1).unwrap() == 0x01, 'top of stack should be 1'); + + ensures_zeros(ref vm.stack, initial_len + 1, new_stack_len - 1); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/environmental_information.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/environmental_information.cairo new file mode 100644 index 000000000..22d47d8a7 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/environmental_information.cairo @@ -0,0 +1,1181 @@ +use core::num::traits::OverflowingAdd; +use core::num::traits::Zero; +use core::num::traits::{CheckedAdd, CheckedSub}; +use crate::errors::{ensure, EVMError}; +use crate::gas; +use crate::memory::MemoryTrait; +use crate::model::account::{AccountTrait}; +use crate::model::vm::{VM, VMTrait}; +use crate::model::{AddressTrait}; +use crate::stack::StackTrait; +use crate::state::StateTrait; +use utils::helpers::bytes_32_words_size; +use utils::set::SetTrait; +use utils::traits::bytes::FromBytes; +use utils::traits::{EthAddressIntoU256}; + + +#[generate_trait] +pub impl EnvironmentInformationImpl of EnvironmentInformationTrait { + /// 0x30 - ADDRESS + /// Get address of currently executing account. + /// # Specification: https://www.evm.codes/#30?fork=shanghai + fn exec_address(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + self.stack.push(self.message().target.evm.into()) + } + + /// 0x31 - BALANCE opcode. + /// Get ETH balance of the specified address. + /// # Specification: https://www.evm.codes/#31?fork=shanghai + fn exec_balance(ref self: VM) -> Result<(), EVMError> { + let evm_address = self.stack.pop_eth_address()?; + + // GAS + if self.accessed_addresses.contains(evm_address) { + self.charge_gas(gas::WARM_ACCESS_COST)?; + } else { + self.accessed_addresses.add(evm_address); + self.charge_gas(gas::COLD_ACCOUNT_ACCESS_COST)? + } + + let balance = self.env.state.get_account(evm_address).balance(); + self.stack.push(balance) + } + + /// 0x32 - ORIGIN + /// Get execution origination address. + /// # Specification: https://www.evm.codes/#32?fork=shanghai + fn exec_origin(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + self.stack.push(self.env.origin.evm.into()) + } + + /// 0x33 - CALLER + /// Get caller address. + /// # Specification: https://www.evm.codes/#33?fork=shanghai + fn exec_caller(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + self.stack.push(self.message().caller.evm.into()) + } + + /// 0x34 - CALLVALUE + /// Get deposited value by the instruction/transaction responsible for this execution. + /// # Specification: https://www.evm.codes/#34?fork=shanghai + fn exec_callvalue(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + self.stack.push(self.message().value) + } + + /// 0x35 - CALLDATALOAD + /// Push a word from the calldata onto the stack. + /// # Specification: https://www.evm.codes/#35?fork=shanghai + fn exec_calldataload(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + + // Don't error out if the offset is too big. It should just push 0. + let offset: usize = self.stack.pop_saturating_usize()?; + + let calldata = self.message().data; + let calldata_len = calldata.len(); + + // All bytes after the end of the calldata are set to 0. + let bytes_len = match calldata_len.checked_sub(offset) { + Option::None => { return self.stack.push(0); }, + Option::Some(remaining_len) => { + if remaining_len == 0 { + return self.stack.push(0); + } + core::cmp::min(32, remaining_len) + } + }; + + // Slice the calldata + let sliced = calldata.slice(offset, bytes_len); + + let mut data_to_load: u256 = sliced + .from_be_bytes_partial() + .expect('Failed to parse calldata'); + + // Fill the rest of the data to load with zeros + // TODO: optimize once we have dw-based exponentiation + for _ in 0..32 - bytes_len { + data_to_load *= 256; + }; + self.stack.push(data_to_load) + } + + /// 0x36 - CALLDATASIZE + /// Get the size of return data. + /// # Specification: https://www.evm.codes/#36?fork=shanghai + fn exec_calldatasize(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + let size: u256 = self.message().data.len().into(); + self.stack.push(size) + } + + /// 0x37 - CALLDATACOPY operation + /// Save word to memory. + /// # Specification: https://www.evm.codes/#37?fork=shanghai + fn exec_calldatacopy(ref self: VM) -> Result<(), EVMError> { + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + let words_size = bytes_32_words_size(size).into(); + let copy_gas_cost = gas::COPY * words_size; + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(dest_offset, size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + + let total_cost = gas::VERYLOW + .checked_add(copy_gas_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let calldata: Span = self.message().data; + copy_bytes_to_memory(ref self, calldata, dest_offset, offset, size); + Result::Ok(()) + } + + /// 0x38 - CODESIZE + /// Get size of bytecode running in current environment. + /// # Specification: https://www.evm.codes/#38?fork=shanghai + fn exec_codesize(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + let size: u256 = self.message().code.len().into(); + self.stack.push(size) + } + + /// 0x39 - CODECOPY + /// Copies slice of bytecode to memory. + /// # Specification: https://www.evm.codes/#39?fork=shanghai + fn exec_codecopy(ref self: VM) -> Result<(), EVMError> { + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + let words_size = bytes_32_words_size(size).into(); + let copy_gas_cost = gas::COPY * words_size; + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(dest_offset, size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + + let total_cost = gas::VERYLOW + .checked_add(copy_gas_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let bytecode: Span = self.message().code; + + copy_bytes_to_memory(ref self, bytecode, dest_offset, offset, size); + Result::Ok(()) + } + + /// 0x3A - GASPRICE + /// Get price of gas in current environment. + /// # Specification: https://www.evm.codes/#3a?fork=shanghai + fn exec_gasprice(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + self.stack.push(self.env.gas_price.into()) + } + + /// 0x3B - EXTCODESIZE + /// Get size of an account's code. + /// # Specification: https://www.evm.codes/#3b?fork=shanghai + fn exec_extcodesize(ref self: VM) -> Result<(), EVMError> { + let evm_address = self.stack.pop_eth_address()?; + + // GAS + if self.accessed_addresses.contains(evm_address) { + self.charge_gas(gas::WARM_ACCESS_COST)?; + } else { + self.accessed_addresses.add(evm_address); + self.charge_gas(gas::COLD_ACCOUNT_ACCESS_COST)? + } + + let account = self.env.state.get_account(evm_address); + self.stack.push(account.code.len().into()) + } + + /// 0x3C - EXTCODECOPY + /// Copy an account's code to memory + /// # Specification: https://www.evm.codes/#3c?fork=shanghai + fn exec_extcodecopy(ref self: VM) -> Result<(), EVMError> { + let evm_address = self.stack.pop_eth_address()?; + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + // GAS + let words_size = bytes_32_words_size(size).into(); + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(dest_offset, size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + let copy_gas_cost = gas::COPY * words_size; + let access_gas_cost = if self.accessed_addresses.contains(evm_address) { + gas::WARM_ACCESS_COST + } else { + self.accessed_addresses.add(evm_address); + gas::COLD_ACCOUNT_ACCESS_COST + }; + let total_cost = access_gas_cost + .checked_add(copy_gas_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let bytecode = self.env.state.get_account(evm_address).code; + copy_bytes_to_memory(ref self, bytecode, dest_offset, offset, size); + Result::Ok(()) + } + + /// 0x3D - RETURNDATASIZE + /// Get the size of return data. + /// # Specification: https://www.evm.codes/#3d?fork=shanghai + fn exec_returndatasize(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + let size = self.return_data().len(); + self.stack.push(size.into()) + } + + /// 0x3E - RETURNDATACOPY + /// Save word to memory. + /// # Specification: https://www.evm.codes/#3e?fork=shanghai + fn exec_returndatacopy(ref self: VM) -> Result<(), EVMError> { + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + let return_data: Span = self.return_data(); + + let (last_returndata_index, overflow) = offset.overflowing_add(size); + if overflow { + return Result::Err(EVMError::ReturnDataOutOfBounds); + } + ensure(!(last_returndata_index > return_data.len()), EVMError::ReturnDataOutOfBounds)?; + + let words_size = bytes_32_words_size(size).into(); + let copy_gas_cost = gas::COPY * words_size; + + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(dest_offset, size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + let total_cost = gas::VERYLOW + .checked_add(copy_gas_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let data_to_copy: Span = return_data.slice(offset, size); + self.memory.store_n(data_to_copy, dest_offset); + + Result::Ok(()) + } + + /// 0x3F - EXTCODEHASH + /// Get hash of a contract's code. + // If the account has no code, return the empty hash: + // `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` + // If the account does not exist, is a precompile or was destroyed (SELFDESTRUCT), return 0 + // Else return, the hash of the account's code + /// # Specification: https://www.evm.codes/#3f?fork=shanghai + fn exec_extcodehash(ref self: VM) -> Result<(), EVMError> { + let evm_address = self.stack.pop_eth_address()?; + + // GAS + if self.accessed_addresses.contains(evm_address) { + self.charge_gas(gas::WARM_ACCESS_COST)?; + } else { + self.accessed_addresses.add(evm_address); + self.charge_gas(gas::COLD_ACCOUNT_ACCESS_COST)? + } + + let account = self.env.state.get_account(evm_address); + // Relevant cases: + // https://github.com/ethereum/go-ethereum/blob/master/core/vm/instructions.go#L392 + if account.evm_address().is_precompile() + || (!account.has_code_or_nonce() && account.balance.is_zero()) { + return self.stack.push(0); + } + self.stack.push(account.code_hash) + } +} + +#[inline(always)] +fn copy_bytes_to_memory( + ref self: VM, bytes: Span, dest_offset: usize, offset: usize, size: usize +) { + let bytes_slice = match bytes.len().checked_sub(offset) { + Option::Some(remaining) => bytes.slice(offset, core::cmp::min(size, remaining)), + Option::None => [].span() + }; + + self.memory.store_padded_segment(dest_offset, size, bytes_slice); +} + + +#[cfg(test)] +mod tests { + use contracts::test_data::counter_evm_bytecode; + use core::starknet::EthAddress; + use crate::errors::{EVMError, TYPE_CONVERSION_ERROR}; + use crate::instructions::EnvironmentInformationTrait; + use crate::memory::{InternalMemoryTrait, MemoryTrait}; + + use crate::model::vm::VMTrait; + use crate::model::{Account, Address}; + use crate::stack::StackTrait; + use crate::state::StateTrait; + use crate::test_utils::{VMBuilderTrait, origin, callvalue, gas_price}; + use snforge_std::test_address; + use utils::constants::EMPTY_KECCAK; + use utils::helpers::{u256_to_bytes_array, compute_starknet_address}; + use utils::traits::array::ArrayExtTrait; + use utils::traits::bytes::{U8SpanExTrait}; + use utils::traits::{EthAddressIntoU256}; + + + mod test_internals { + use crate::memory::MemoryTrait; + use crate::test_utils::VMBuilderTrait; + use super::super::copy_bytes_to_memory; + + fn test_copy_bytes_to_memory_helper( + bytes: Span, dest_offset: usize, offset: usize, size: usize, expected: Span + ) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + copy_bytes_to_memory(ref vm, bytes, dest_offset, offset, size); + + // Then + let mut result = ArrayTrait::new(); + vm.memory.load_n(size, ref result, dest_offset); + assert_eq!(result.span(), expected); + } + + #[test] + fn test_copy_bytes_to_memory_normal_case() { + let bytes = [1, 2, 3, 4, 5].span(); + test_copy_bytes_to_memory_helper(bytes, 0, 0, 5, bytes); + } + + #[test] + fn test_copy_bytes_to_memory_with_offset() { + let bytes = [1, 2, 3, 4, 5].span(); + test_copy_bytes_to_memory_helper(bytes, 0, 2, 3, [3, 4, 5].span()); + } + + #[test] + fn test_copy_bytes_to_memory_size_larger_than_remaining_bytes() { + let bytes = [1, 2, 3, 4, 5].span(); + test_copy_bytes_to_memory_helper(bytes, 0, 3, 5, [4, 5, 0, 0, 0].span()); + } + + #[test] + fn test_copy_bytes_to_memory_offset_out_of_bounds() { + let bytes = [1, 2, 3, 4, 5].span(); + test_copy_bytes_to_memory_helper(bytes, 0, 10, 5, [0, 0, 0, 0, 0].span()); + } + + #[test] + fn test_copy_bytes_to_memory_zero_size() { + let bytes = [1, 2, 3, 4, 5].span(); + test_copy_bytes_to_memory_helper(bytes, 0, 0, 0, [].span()); + } + + #[test] + fn test_copy_bytes_to_memory_non_zero_dest_offset() { + let bytes = [1, 2, 3, 4, 5].span(); + test_copy_bytes_to_memory_helper(bytes, 10, 0, 5, bytes); + } + } + + // ************************************************************************* + // 0x30: ADDRESS + // ************************************************************************* + + #[test] + fn test_address_basic() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_address().expect('exec_address failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop_eth_address().unwrap(), vm.message().target.evm.into()); + } + + // ************************************************************************* + // 0x31: BALANCE + // ************************************************************************* + #[test] + fn test_exec_balance_eoa() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 400, + nonce: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(vm.message.target.evm.into()).unwrap(); + + // When + vm.exec_balance().expect('exec_balance failed'); + + // Then + assert_eq!(vm.stack.peek().unwrap(), 400); + } + + // ************************************************************************* + // 0x33: CALLER + // ************************************************************************* + #[test] + fn test_caller() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_caller().expect('exec_caller failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), origin().into()); + } + + + // ************************************************************************* + // 0x32: ORIGIN + // ************************************************************************* + #[test] + fn test_origin_nested_ctx() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_origin().expect('exec_origin failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), vm.env.origin.evm.into()); + } + + + // ************************************************************************* + // 0x34: CALLVALUE + // ************************************************************************* + #[test] + fn test_exec_callvalue() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_callvalue().expect('exec_callvalue failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop().unwrap(), callvalue()); + } + + // ************************************************************************* + // 0x35: CALLDATALOAD + // ************************************************************************* + + #[test] + fn test_calldataload() { + // Given + let calldata = u256_to_bytes_array( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + let mut vm = VMBuilderTrait::new_with_presets().with_calldata(calldata.span()).build(); + + let offset: u32 = 0; + vm.stack.push(offset.into()).expect('push failed'); + + // When + vm.exec_calldataload().expect('exec_calldataload failed'); + + // Then + let result: u256 = vm.stack.pop().unwrap(); + assert_eq!(result, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_calldataload_with_offset() { + // Given + let calldata = u256_to_bytes_array( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + + let mut vm = VMBuilderTrait::new_with_presets().with_calldata(calldata.span()).build(); + + let offset: u32 = 31; + vm.stack.push(offset.into()).expect('push failed'); + + // When + vm.exec_calldataload().expect('exec_calldataload failed'); + + // Then + let result: u256 = vm.stack.pop().unwrap(); + + assert_eq!(result, 0xFF00000000000000000000000000000000000000000000000000000000000000); + } + + #[test] + fn test_calldataload_with_offset_beyond_calldata() { + // Given + let calldata = u256_to_bytes_array( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + let mut vm = VMBuilderTrait::new_with_presets().with_calldata(calldata.span()).build(); + + let offset: u32 = calldata.len() + 1; + vm.stack.push(offset.into()).expect('push failed'); + + // When + vm.exec_calldataload().expect('exec_calldataload failed'); + + // Then + let result: u256 = vm.stack.pop().unwrap(); + assert_eq!(result, 0); + } + + #[test] + fn test_calldataload_with_function_selector() { + // Given + let calldata = array![0x6d, 0x4c, 0xe6, 0x3c]; + let mut vm = VMBuilderTrait::new_with_presets().with_calldata(calldata.span()).build(); + + let offset: u32 = 0; + vm.stack.push(offset.into()).expect('push failed'); + + // When + vm.exec_calldataload().expect('exec_calldataload failed'); + + // Then + let result: u256 = vm.stack.pop().unwrap(); + assert_eq!(result, 0x6d4ce63c00000000000000000000000000000000000000000000000000000000); + } + + + #[test] + fn test_calldataload_with_offset_bigger_usize_succeeds() { + // Given + let calldata = u256_to_bytes_array( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + let mut vm = VMBuilderTrait::new_with_presets().with_calldata(calldata.span()).build(); + let offset: u256 = 5000000000; + vm.stack.push(offset).expect('push failed'); + + // When + let result = vm.exec_calldataload(); + + // Then + assert!(result.is_ok()); + assert_eq!(vm.stack.pop().unwrap(), 0); + } + + // ************************************************************************* + // 0x36: CALLDATASIZE + // ************************************************************************* + + #[test] + fn test_calldata_size() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let calldata: Span = vm.message.data; + + // When + vm.exec_calldatasize().expect('exec_calldatasize failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), calldata.len().into()); + } + + // ************************************************************************* + // 0x37: CALLDATACOPY + // ************************************************************************* + + #[test] + fn test_calldatacopy_type_conversion_error() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + + // When + let res = vm.exec_calldatacopy(); + + // Then + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); + } + + #[test] + fn test_calldatacopy_basic() { + test_calldatacopy(32, 0, 3, [4, 5, 6].span()); + } + + #[test] + fn test_calldatacopy_with_out_of_bound_bytes() { + // For out of bound bytes, 0s will be copied. + let mut expected = array![4, 5, 6]; + expected.append_n(0, 5); + + test_calldatacopy(32, 0, 8, expected.span()); + } + + #[test] + fn test_calldatacopy_with_out_of_bound_bytes_multiple_words() { + // For out of bound bytes, 0s will be copied. + let mut expected = array![4, 5, 6]; + expected.append_n(0, 31); + + test_calldatacopy(32, 0, 34, expected.span()); + } + + fn test_calldatacopy(dest_offset: u32, offset: u32, mut size: u32, expected: Span) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let _calldata: Span = vm.message.data; + + vm.stack.push(size.into()).expect('push failed'); + vm.stack.push(offset.into()).expect('push failed'); + vm.stack.push(dest_offset.into()).expect('push failed'); + + // Memory initialization with a value to verify that if the offset + size is out of the + // bound bytes, 0's have been copied. + // Otherwise, the memory value would be 0, and we wouldn't be able to check it. + for i in 0 + ..(size / 32) + + 1 { + vm + .memory + .store( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + dest_offset + (i * 32) + ); + + let initial: u256 = vm.memory.load_internal(dest_offset + (i * 32)).into(); + + assert_eq!( + initial, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + }; + + // When + vm.exec_calldatacopy().expect('exec_calldatacopy failed'); + + // Then + assert!(vm.stack.is_empty()); + + let mut results: Array = ArrayTrait::new(); + vm.memory.load_n_internal(size, ref results, dest_offset); + + assert_eq!(results.span(), expected); + } + + // ************************************************************************* + // 0x38: CODESIZE + // ************************************************************************* + + #[test] + fn test_codesize() { + // Given + let bytecode: Span = [1, 2, 3, 4, 5].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + // When + vm.exec_codesize().expect('exec_codesize failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop().unwrap(), bytecode.len().into()); + } + + // ************************************************************************* + // 0x39: CODECOPY + // ************************************************************************* + + #[test] + fn test_codecopy_type_conversion_error() { + // Given + let bytecode: Span = [1, 2, 3, 4, 5].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + + // When + let res = vm.exec_codecopy(); + + // Then + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); + } + + #[test] + fn test_codecopy_basic() { + test_codecopy(32, 0, 0); + } + + #[test] + fn test_codecopy_with_out_of_bound_bytes() { + test_codecopy(32, 0, 8); + } + + #[test] + fn test_codecopy_with_out_of_bound_offset() { + test_codecopy(0, 0xFFFFFFFE, 2); + } + + fn test_codecopy(dest_offset: u32, offset: u32, mut size: u32) { + // Given + let bytecode: Span = [1, 2, 3, 4, 5].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + if (size == 0) { + size = bytecode.len() - offset; + } + + vm.stack.push(size.into()).expect('push failed'); + vm.stack.push(offset.into()).expect('push failed'); + vm.stack.push(dest_offset.into()).expect('push failed'); + + vm + .memory + .store(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, dest_offset); + let initial: u256 = vm.memory.load_internal(dest_offset).into(); + assert_eq!(initial, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + + // When + vm.exec_codecopy().expect('exec_codecopy failed'); + + // Then + assert!(vm.stack.is_empty()); + + let result: u256 = vm.memory.load_internal(dest_offset).into(); + let mut results: Array = u256_to_bytes_array(result); + + for i in 0 + ..size { + // For out of bound bytes, 0s will be copied. + if (i + offset >= bytecode.len()) { + assert_eq!(*results[i], 0); + } else { + assert_eq!(*results[i], *bytecode[i + offset]); + } + }; + } + + // ************************************************************************* + // 0x3A: GASPRICE + // ************************************************************************* + + #[test] + fn test_gasprice() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_gasprice().expect('exec_gasprice failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), gas_price().into()); + } + + // ************************************************************************* + // 0x3B - EXTCODESIZE + // ************************************************************************* + #[test] + fn test_exec_extcodesize_should_push_bytecode_len_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: [].span(), + code_hash: EMPTY_KECCAK, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); + + // When + vm.exec_extcodesize().unwrap(); + + // Then + assert_eq!(vm.stack.peek().unwrap(), 0); + } + + #[test] + fn test_exec_extcodesize_should_push_bytecode_len() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let bytecode = [0xff; 350].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: bytecode, + code_hash, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); + + // When + vm.exec_extcodesize().unwrap(); + + // Then + assert_eq!(vm.stack.peek().unwrap(), 350); + } + + // ************************************************************************* + // 0x3C - EXTCODECOPY + // ************************************************************************* + + #[test] + fn test_exec_extcodecopy_should_copy_code_of_input_account() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let bytecode = counter_evm_bytecode(); + let code_hash = bytecode.compute_keccak256_hash(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: bytecode, + code_hash, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + + vm.stack.push(account.address.evm.into()).expect('push failed'); + // size + vm.stack.push(50).expect('push failed'); + // offset + vm.stack.push(200).expect('push failed'); + // destOffset (memory offset) + vm.stack.push(20).expect('push failed'); + vm.stack.push(account.address.evm.into()).unwrap(); + + // When + vm.exec_extcodecopy().unwrap(); + + // Then + let mut bytecode_slice = array![]; + vm.memory.load_n(50, ref bytecode_slice, 20); + assert_eq!(bytecode_slice.span(), account.code.slice(200, 50)); + } + + #[test] + fn test_exec_extcodecopy_ca_offset_out_of_bounds_should_return_zeroes() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let bytecode = counter_evm_bytecode(); + let code_hash = bytecode.compute_keccak256_hash(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: bytecode, + code_hash, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); + + // size + vm.stack.push(5).expect('push failed'); + // offset + vm.stack.push(5000).expect('push failed'); + // destOffset + vm.stack.push(20).expect('push failed'); + vm.stack.push(account.address.evm.into()).expect('push failed'); + + // When + vm.exec_extcodecopy().unwrap(); + + // Then + let mut bytecode_slice = array![]; + vm.memory.load_n(5, ref bytecode_slice, 20); + assert_eq!(bytecode_slice.span(), [0, 0, 0, 0, 0].span()); + } + + #[test] + fn test_exec_returndatasize() { + // Given + let return_data: Array = array![1, 2, 3, 4, 5]; + let size = return_data.len(); + + let mut vm = VMBuilderTrait::new_with_presets() + .with_return_data(return_data.span()) + .build(); + + vm.exec_returndatasize().expect('exec_returndatasize failed'); + + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.pop().unwrap(), size.into()); + } + + // ************************************************************************* + // 0x3E: RETURNDATACOPY + // ************************************************************************* + + #[test] + fn test_returndata_copy_type_conversion_error() { + // Given + let return_data: Array = array![1, 2, 3, 4, 5]; + let mut vm = VMBuilderTrait::new_with_presets() + .with_return_data(return_data.span()) + .build(); + + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + .expect('push failed'); + + // When + let res = vm.exec_returndatacopy(); + + // Then + assert_eq!(res.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); + } + + fn test_returndatacopy_helper( + return_data: Span, + dest_offset: u32, + offset: u32, + size: u32, + expected_result: Result, EVMError> + ) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_return_data(return_data).build(); + + vm.stack.push(size.into()).expect('push failed'); + vm.stack.push(offset.into()).expect('push failed'); + vm.stack.push(dest_offset.into()).expect('push failed'); + + // When + let res = vm.exec_returndatacopy(); + + // Then + match expected_result { + Result::Ok(expected) => { + assert!(res.is_ok()); + let mut result = ArrayTrait::new(); + vm + .memory + .load_n(size.try_into().unwrap(), ref result, dest_offset.try_into().unwrap()); + assert_eq!(result.span(), expected); + }, + Result::Err(expected_error) => { + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), expected_error); + } + } + } + + #[test] + fn test_returndatacopy_basic() { + let return_data = array![1, 2, 3, 4, 5].span(); + test_returndatacopy_helper(return_data, 0, 0, 5, Result::Ok(return_data)); + } + + #[test] + fn test_returndatacopy_with_offset() { + let return_data = array![1, 2, 3, 4, 5].span(); + test_returndatacopy_helper(return_data, 0, 2, 3, Result::Ok([3, 4, 5].span())); + } + + #[test] + fn test_returndatacopy_out_of_bounds() { + let return_data = array![1, 2, 3, 4, 5].span(); + test_returndatacopy_helper( + return_data, 0, 3, 3, Result::Err(EVMError::ReturnDataOutOfBounds) + ); + } + + #[test] + fn test_returndatacopy_overflowing_add() { + let return_data = array![1, 2, 3, 4, 5].span(); + test_returndatacopy_helper( + return_data, 0, 0xFFFFFFFF, 1, Result::Err(EVMError::ReturnDataOutOfBounds) + ); + } + + // ************************************************************************* + // 0x3F: EXTCODEHASH + // ************************************************************************* + #[test] + fn test_exec_extcodehash_precompile() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let precompile_evm_address: EthAddress = evm::precompiles::LAST_ETHEREUM_PRECOMPILE_ADDRESS + .try_into() + .unwrap(); + let precompile_starknet_address = compute_starknet_address( + test_address(), precompile_evm_address, 0.try_into().unwrap() + ); + let account = Account { + address: Address { + evm: precompile_evm_address, starknet: precompile_starknet_address, + }, + balance: 1, + nonce: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(precompile_evm_address.into()).expect('push failed'); + + // When + vm.exec_extcodehash().unwrap(); + + // Then + assert_eq!(vm.stack.peek().unwrap(), 0); + } + + #[test] + fn test_exec_extcodehash_empty_account() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 0, + nonce: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); + + // When + vm.exec_extcodehash().unwrap(); + + // Then + assert_eq!(vm.stack.peek().unwrap(), 0); + } + + #[test] + fn test_exec_extcodehash_no_bytecode() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: [].span(), + code_hash: EMPTY_KECCAK, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); + + // When + vm.exec_extcodehash().unwrap(); + + // Then + assert_eq!(vm.stack.peek().unwrap(), EMPTY_KECCAK); + } + + #[test] + fn test_exec_extcodehash_with_bytecode() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let bytecode = counter_evm_bytecode(); + let code_hash = bytecode.compute_keccak256_hash(); + let account = Account { + address: vm.message().target, + balance: 1, + nonce: 1, + code: bytecode, + code_hash, + selfdestruct: false, + is_created: true, + }; + vm.env.state.set_account(account); + vm.stack.push(account.address.evm.into()).expect('push failed'); + + // When + vm.exec_extcodehash().unwrap(); + + // Then + assert_eq!( + vm.stack.peek() // extcodehash(Counter.sol) := + // 0x82abf19c13d2262cc530f54956af7e4ec1f45f637238ed35ed7400a3409fd275 (source: + // remix) + // + .unwrap(), + 0xec976f44607e73ea88910411e3da156757b63bea5547b169e1e0d733443f73b0, + ); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/exchange_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/exchange_operations.cairo new file mode 100644 index 000000000..349e0f3c1 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/exchange_operations.cairo @@ -0,0 +1,439 @@ +//! Exchange Operations. + +use crate::errors::EVMError; +use crate::gas; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; + +/// Place i bytes items on stack. +#[inline(always)] +fn exec_swap_i(ref self: VM, i: u8) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + self.stack.swap_i(i.into()) +} + +#[generate_trait] +pub impl ExchangeOperations of ExchangeOperationsTrait { + /// 0x90 - SWAP1 operation + /// Exchange 1st and 2nd stack items. + /// # Specification: https://www.evm.codes/#90?fork=shanghai + + fn exec_swap1(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 1) + } + + /// 0x91 - SWAP2 operation + /// Exchange 1st and 3rd stack items. + /// # Specification: https://www.evm.codes/#91?fork=shanghai + fn exec_swap2(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 2) + } + + /// 0x92 - SWAP3 operation + /// Exchange 1st and 4th stack items. + /// # Specification: https://www.evm.codes/#92?fork=shanghai + fn exec_swap3(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 3) + } + + /// 0x93 - SWAP4 operation + /// Exchange 1st and 5th stack items. + /// # Specification: https://www.evm.codes/#93?fork=shanghai + fn exec_swap4(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 4) + } + + /// 0x94 - SWAP5 operation + /// Exchange 1st and 6th stack items. + /// # Specification: https://www.evm.codes/#94?fork=shanghai + fn exec_swap5(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 5) + } + + /// 0x95 - SWAP6 operation + /// Exchange 1st and 7th stack items. + /// # Specification: https://www.evm.codes/#95?fork=shanghai + fn exec_swap6(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 6) + } + + /// 0x96 - SWAP7 operation + /// Exchange 1st and 8th stack items. + /// # Specification: https://www.evm.codes/#96?fork=shanghai + fn exec_swap7(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 7) + } + + /// 0x97 - SWAP8 operation + /// Exchange 1st and 9th stack items. + /// # Specification: https://www.evm.codes/#97?fork=shanghai + fn exec_swap8(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 8) + } + + /// 0x98 - SWAP9 operation + /// Exchange 1st and 10th stack items. + /// # Specification: https://www.evm.codes/#98?fork=shanghai + fn exec_swap9(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 9) + } + + /// 0x99 - SWAP10 operation + /// Exchange 1st and 11th stack items. + /// # Specification: https://www.evm.codes/#99?fork=shanghai + fn exec_swap10(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 10) + } + + /// 0x9A - SWAP11 operation + /// Exchange 1st and 12th stack items. + /// # Specification: https://www.evm.codes/#9a?fork=shanghai + fn exec_swap11(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 11) + } + + /// 0x9B - SWAP12 operation + /// Exchange 1st and 13th stack items. + /// # Specification: https://www.evm.codes/#9b?fork=shanghai + fn exec_swap12(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 12) + } + + /// 0x9C - SWAP13 operation + /// Exchange 1st and 14th stack items. + /// # Specification: https://www.evm.codes/#9c?fork=shanghai + fn exec_swap13(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 13) + } + + /// 0x9D - SWAP14 operation + /// Exchange 1st and 15th stack items. + /// # Specification: https://www.evm.codes/#9d?fork=shanghai + fn exec_swap14(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 14) + } + + /// 0x9E - SWAP15 operation + /// Exchange 1st and 16th stack items. + /// # Specification: https://www.evm.codes/#9e?fork=shanghai + fn exec_swap15(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 15) + } + + /// 0x9F - SWAP16 operation + /// Exchange 1st and 16th stack items. + /// # Specification: https://www.evm.codes/#9f?fork=shanghai + fn exec_swap16(ref self: VM) -> Result<(), EVMError> { + exec_swap_i(ref self, 16) + } +} + + +#[cfg(test)] +mod tests { + use crate::instructions::exchange_operations::ExchangeOperationsTrait; + use crate::stack::StackTrait; + use crate::test_utils::VMBuilderTrait; + + + #[test] + fn test_exec_swap1() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap1().expect('exec_swap1 failed'); + + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(1).unwrap() == 1, 'val at index 1 should be now 1'); + } + + + #[test] + fn test_exec_swap2() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap2().expect('exec_swap2 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(2).unwrap() == 1, 'val at index 2 should be now 1'); + } + + #[test] + fn test_exec_swap3() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap3().expect('exec_swap3 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(3).unwrap() == 1, 'val at index 3 should be now 1'); + } + + #[test] + fn test_exec_swap4() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap4().expect('exec_swap4 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(4).unwrap() == 1, 'val at index 4 should be now 1'); + } + + + #[test] + fn test_exec_swap5() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap5().expect('exec_swap5 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(5).unwrap() == 1, 'val at index 5 should be now 1'); + } + + #[test] + fn test_exec_swap6() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap6().expect('exec_swap6 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(6).unwrap() == 1, 'val at index 6 should be now 1'); + } + + + #[test] + fn test_exec_swap7() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap7().expect('exec_swap7 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(7).unwrap() == 1, 'val at index 7 should be now 1'); + } + + #[test] + fn test_exec_swap8() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap8().expect('exec_swap8 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(8).unwrap() == 1, 'val at index 8 should be now 1'); + } + + + #[test] + fn test_exec_swap9() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap9().expect('exec_swap9 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(9).unwrap() == 1, 'val at index 9 should be now 1'); + } + + #[test] + fn test_exec_swap10() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap10().expect('exec_swap10 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(10).unwrap() == 1, 'val at index 10 should be now 1'); + } + + #[test] + fn test_exec_swap11() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap11().expect('exec_swap11 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(11).unwrap() == 1, 'val at index 11 should be now 1'); + } + + #[test] + fn test_exec_swap12() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap12().expect('exec_swap12 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(12).unwrap() == 1, 'val at index 12 should be now 1'); + } + + #[test] + fn test_exec_swap13() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap13().expect('exec_swap13 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(13).unwrap() == 1, 'val at index 13 should be now 1'); + } + + #[test] + fn test_exec_swap14() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap14().expect('exec_swap14 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(14).unwrap() == 1, 'val at index 14 should be now 1'); + } + + #[test] + fn test_exec_swap15() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap15().expect('exec_swap15 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(15).unwrap() == 1, 'val at index 15 should be now 1'); + } + + #[test] + fn test_exec_swap16() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + // given + vm.stack.push(0xf).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + vm.exec_swap16().expect('exec_swap16 failed'); + assert(vm.stack.peek().unwrap() == 0xf, 'Top should be now 0xf'); + assert(vm.stack.peek_at(16).unwrap() == 1, 'val at index 16 should be now 1'); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/logging_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/logging_operations.cairo new file mode 100644 index 000000000..4be7153b5 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/logging_operations.cairo @@ -0,0 +1,404 @@ +//! Logging Operations. + +use core::num::traits::CheckedAdd; +use crate::errors::{EVMError, ensure}; +use crate::gas; +use crate::memory::MemoryTrait; +use crate::model::Event; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; +use crate::state::StateTrait; + + +#[generate_trait] +pub impl LoggingOperations of LoggingOperationsTrait { + /// 0xA0 - LOG0 operation + /// Append log record with no topic. + /// # Specification: https://www.evm.codes/#a0?fork=shanghai + fn exec_log0(ref self: VM) -> Result<(), EVMError> { + exec_log_i(ref self, 0) + } + + /// 0xA1 - LOG1 + /// Append log record with one topic. + /// # Specification: https://www.evm.codes/#a1?fork=shanghai + fn exec_log1(ref self: VM) -> Result<(), EVMError> { + exec_log_i(ref self, 1) + } + + /// 0xA2 - LOG2 + /// Append log record with two topics. + /// # Specification: https://www.evm.codes/#a2?fork=shanghai + fn exec_log2(ref self: VM) -> Result<(), EVMError> { + exec_log_i(ref self, 2) + } + + /// 0xA3 - LOG3 + /// Append log record with three topics. + /// # Specification: https://www.evm.codes/#a3?fork=shanghai + fn exec_log3(ref self: VM) -> Result<(), EVMError> { + exec_log_i(ref self, 3) + } + + /// 0xA4 - LOG4 + /// Append log record with four topics. + /// # Specification: https://www.evm.codes/#a4?fork=shanghai + fn exec_log4(ref self: VM) -> Result<(), EVMError> { + exec_log_i(ref self, 4) + } +} + + +/// Store a new event in the dynamic context using topics +/// popped from the stack and data from the memory. +/// +/// # Arguments +/// +/// * `self` - The context to which the event will be added +/// * `topics_len` - The amount of topics to pop from the stack +fn exec_log_i(ref self: VM, topics_len: u8) -> Result<(), EVMError> { + // Revert if the transaction is in a read only context + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + + // TODO(optimization): check benefits of n `pop` instead of `pop_n` + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + let topics: Array = self.stack.pop_n(topics_len.into())?; + + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + + // TODO: avoid addition overflows here. We should use checked arithmetic. + let total_cost = gas::LOG + .checked_add(topics_len.into() * gas::LOGTOPIC) + .ok_or(EVMError::OutOfGas)? + .checked_add(size.into() * gas::LOGDATA) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let mut data: Array = Default::default(); + self.memory.load_n(size, ref data, offset); + + let event: Event = Event { keys: topics, data }; + self.env.state.add_event(event); + + Result::Ok(()) +} + +#[cfg(test)] +mod tests { + use core::num::traits::Bounded; + use core::result::ResultTrait; + use crate::errors::{EVMError, TYPE_CONVERSION_ERROR}; + use crate::instructions::LoggingOperationsTrait; + use crate::stack::StackTrait; + use crate::test_utils::{VMBuilderTrait, MemoryTestUtilsTrait}; + use utils::helpers::u256_to_bytes_array; + + #[test] + fn test_exec_log0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + + vm.stack.push(0x1F).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + let result = vm.exec_log0(); + + // Then + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); + + let mut events = vm.env.state.events; + assert_eq!(events.len(), 1); + + let event = events.pop_front().unwrap(); + assert_eq!(event.keys.len(), 0); + + assert_eq!(event.data.len(), 31); + let data_expected = u256_to_bytes_array(Bounded::::MAX).span().slice(0, 31); + assert_eq!(event.data.span(), data_expected); + } + + #[test] + fn test_exec_log1() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x20).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + let result = vm.exec_log1(); + + // Then + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); + + let mut events = vm.env.state.events; + assert_eq!(events.len(), 1); + + let event = events.pop_front().unwrap(); + assert_eq!(event.keys.len(), 1); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF].span()); + + assert_eq!(event.data.len(), 32); + let data_expected = u256_to_bytes_array(Bounded::::MAX).span().slice(0, 32); + assert_eq!(event.data.span(), data_expected); + } + + #[test] + fn test_exec_log2() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x05).expect('push failed'); + vm.stack.push(0x05).expect('push failed'); + + // When + let result = vm.exec_log2(); + + // Then + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); + + let mut events = vm.env.state.events; + assert_eq!(events.len(), 1); + + let event = events.pop_front().unwrap(); + assert_eq!(event.keys.len(), 2); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF, Bounded::::MAX].span()); + + assert_eq!(event.data.len(), 5); + let data_expected = u256_to_bytes_array(Bounded::::MAX).span().slice(0, 5); + assert_eq!(event.data.span(), data_expected); + } + + #[test] + fn test_exec_log3() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + vm + .memory + .store_with_expansion( + 0x0123456789ABCDEF000000000000000000000000000000000000000000000000, 0x20 + ); + + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x28).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + let result = vm.exec_log3(); + + // Then + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); + + let mut events = vm.env.state.events; + assert_eq!(events.len(), 1); + + let event = events.pop_front().unwrap(); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF, Bounded::::MAX, 0x00].span()); + + assert_eq!(event.data.len(), 40); + let data_expected = u256_to_bytes_array(Bounded::::MAX).span(); + assert_eq!(event.data.span().slice(0, 32), data_expected); + let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF].span(); + assert_eq!(event.data.span().slice(32, 8), data_expected); + } + + #[test] + fn test_exec_log4() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + vm + .memory + .store_with_expansion( + 0x0123456789ABCDEF000000000000000000000000000000000000000000000000, 0x20 + ); + + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x0A).expect('push failed'); + vm.stack.push(0x20).expect('push failed'); + + // When + let result = vm.exec_log4(); + + // Then + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); + + let mut events = vm.env.state.events; + assert_eq!(events.len(), 1); + + let event = events.pop_front().unwrap(); + assert_eq!( + event.keys.span(), + [0x0123456789ABCDEF, Bounded::::MAX, 0x00, Bounded::::MAX].span() + ); + + assert_eq!(event.data.len(), 10); + let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x00, 0x00].span(); + assert_eq!(event.data.span(), data_expected); + } + + #[test] + fn test_exec_log1_read_only_context() { + // Given + let mut vm = VMBuilderTrait::new().with_read_only().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x20).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + let result = vm.exec_log1(); + + // Then + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::WriteInStaticContext); + } + + #[test] + fn test_exec_log1_size_0_offset_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + let result = vm.exec_log1(); + + // Then + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); + + let mut events = vm.env.state.events; + assert_eq!(events.len(), 1); + + let event = events.pop_front().unwrap(); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF].span()); + + assert_eq!(event.data.len(), 0); + } + + #[test] + fn test_exec_log1_size_too_big() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + let result = vm.exec_log1(); + + // Then + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); + } + + #[test] + fn test_exec_log1_offset_too_big() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x20).expect('push failed'); + vm.stack.push(Bounded::::MAX).expect('push failed'); + + // When + let result = vm.exec_log1(); + + // Then + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::MemoryLimitOOG); + } + + #[test] + fn test_exec_log_multiple_events() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.memory.store_with_expansion(Bounded::::MAX, 0); + vm + .memory + .store_with_expansion( + 0x0123456789ABCDEF000000000000000000000000000000000000000000000000, 0x20 + ); + + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x0A).expect('push failed'); + vm.stack.push(0x20).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x0123456789ABCDEF).expect('push failed'); + vm.stack.push(0x28).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + vm.exec_log3().expect('exec_log3 failed'); + vm.exec_log4().expect('exec_log4 failed'); + + // Then + assert!(vm.stack.is_empty()); + + let mut events = vm.env.state.events; + assert_eq!(events.len(), 2); + + let event1 = events.pop_front().unwrap(); + assert_eq!(event1.keys.span(), [0x0123456789ABCDEF, Bounded::::MAX, 0x00].span()); + + assert_eq!(event1.data.len(), 40); + let data_expected = u256_to_bytes_array(Bounded::::MAX).span(); + assert_eq!(event1.data.span().slice(0, 32), data_expected); + let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF].span(); + assert_eq!(event1.data.span().slice(32, 8), data_expected); + + let event2 = events.pop_front().unwrap(); + assert_eq!( + event2.keys.span(), + [0x0123456789ABCDEF, Bounded::::MAX, 0x00, Bounded::::MAX].span() + ); + + assert_eq!(event2.data.len(), 10); + let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x00, 0x00].span(); + assert_eq!(event2.data.span(), data_expected); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/memory_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/memory_operations.cairo new file mode 100644 index 000000000..4386d275b --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/memory_operations.cairo @@ -0,0 +1,1089 @@ +use core::cmp::max; +use core::num::traits::CheckedAdd; +use crate::backend::starknet_backend::fetch_original_storage; +//! Stack Memory Storage and Flow Operations. +use crate::errors::{EVMError, ensure}; +use crate::gas; +use crate::memory::MemoryTrait; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; +use crate::state::StateTrait; +use utils::helpers::bytes_32_words_size; +use utils::set::SetTrait; +#[inline(always)] +fn jump(ref self: VM, index: usize) -> Result<(), EVMError> { + match self.message().code.get(index) { + Option::Some(_) => { ensure(self.is_valid_jump(index), EVMError::InvalidJump)?; }, + Option::None => { return Result::Err(EVMError::InvalidJump); } + } + self.set_pc(index); + Result::Ok(()) +} + +#[generate_trait] +pub impl MemoryOperation of MemoryOperationTrait { + /// 0x50 - POP operation. + /// Pops the first item on the stack (top of the stack). + /// # Specification: https://www.evm.codes/#50?fork=shanghai + fn exec_pop(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + // self.stack.pop() returns a Result so we cannot simply return its result + self.stack.pop()?; + Result::Ok(()) + } + + /// MLOAD operation. + /// Load word from memory and push to stack. + fn exec_mload(ref self: VM) -> Result<(), EVMError> { + let offset: usize = self + .stack + .pop_usize()?; // Any offset bigger than a usize would MemoryOOG. + + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, 32)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + let total_cost = gas::VERYLOW + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let result = self.memory.load(offset); + self.stack.push(result) + } + + /// 0x52 - MSTORE operation. + /// Save word to memory. + /// # Specification: https://www.evm.codes/#52?fork=shanghai + fn exec_mstore(ref self: VM) -> Result<(), EVMError> { + let offset: usize = self + .stack + .pop_usize()?; // Any offset bigger than a usize would MemoryOOG. + let value: u256 = self.stack.pop()?; + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, 32)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + let total_cost = gas::VERYLOW + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + self.memory.store(value, offset); + Result::Ok(()) + } + + /// 0x53 - MSTORE8 operation. + /// Save single byte to memory + /// # Specification: https://www.evm.codes/#53?fork=shanghai + fn exec_mstore8(ref self: VM) -> Result<(), EVMError> { + let offset = self.stack.pop_usize()?; // Any offset bigger than a usize would MemoryOOG. + let value = self.stack.pop()?; + let value: u8 = (value.low & 0xFF).try_into().unwrap(); + + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, 1)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + let total_cost = gas::VERYLOW + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + self.memory.store_byte(value, offset); + + Result::Ok(()) + } + + + /// 0x54 - SLOAD operation + /// Load from storage. + /// # Specification: https://www.evm.codes/#54?fork=shanghai + fn exec_sload(ref self: VM) -> Result<(), EVMError> { + let key = self.stack.pop()?; + let evm_address = self.message().target.evm; + + // GAS + if self.accessed_storage_keys.contains((evm_address, key)) { + self.charge_gas(gas::WARM_ACCESS_COST)?; + } else { + self.accessed_storage_keys.add((evm_address, key)); + self.charge_gas(gas::COLD_SLOAD_COST)?; + } + + let value = self.env.state.read_state(evm_address, key); + self.stack.push(value) + } + + + /// 0x55 - SSTORE operation + /// Save 32-byte word to storage. + /// # Specification: https://www.evm.codes/#55?fork=shanghai + fn exec_sstore(ref self: VM) -> Result<(), EVMError> { + let key = self.stack.pop()?; + let new_value = self.stack.pop()?; + ensure(self.gas_left() > gas::CALL_STIPEND, EVMError::OutOfGas)?; // EIP-1706 + + let evm_address = self.message().target.evm; + let account = self.env.state.get_account(evm_address); + let original_value = fetch_original_storage(@account, key); + let current_value = self.env.state.read_state(evm_address, key); + + // GAS + let mut gas_cost = 0; + if !self.accessed_storage_keys.contains((evm_address, key)) { + self.accessed_storage_keys.add((evm_address, key)); + gas_cost += gas::COLD_SLOAD_COST; + } + + if original_value == current_value && current_value != new_value { + if original_value == 0 { + gas_cost += gas::SSTORE_SET + } else { + gas_cost += gas::SSTORE_RESET - gas::COLD_SLOAD_COST; + } + } else { + gas_cost += gas::WARM_ACCESS_COST; + } + + // Gas refunds + if current_value != new_value { + if original_value != 0 && current_value != 0 && new_value == 0 { + // Storage is cleared for the first time in the transaction + self.gas_refund += gas::REFUND_SSTORE_CLEARS; + } + + if original_value != 0 && current_value == 0 { + // Earlier gas refund needs to be reversed + self.gas_refund -= gas::REFUND_SSTORE_CLEARS; + } + + if original_value == new_value { + // Restoring slot to original value (used as transient storage) + if original_value == 0 { + // The access cost is still charged but the SSTORE cost is refunded + self.gas_refund += (gas::SSTORE_SET - gas::WARM_ACCESS_COST); + } else { + // Slot was originally non-empty and was updated earlier + // cold sload cost and warm access cost are not refunded + self + .gas_refund += + (gas::SSTORE_RESET - gas::COLD_SLOAD_COST - gas::WARM_ACCESS_COST); + } + } + } + + self.charge_gas(gas_cost)?; + + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + + self.env.state.write_state(:evm_address, :key, value: new_value); + Result::Ok(()) + } + + + /// 0x56 - JUMP operation + /// The JUMP instruction changes the pc counter. + /// The new pc target has to be a JUMPDEST opcode. + /// # Specification: https://www.evm.codes/#56?fork=shanghai + /// + /// Valid jump destinations are defined as follows: + /// * The jump destination is less than the length of the code. + /// * The jump destination should have the `JUMPDEST` opcode (0x5B). + /// * The jump destination shouldn't be part of the data corresponding to + /// `PUSH-N` opcodes. + /// + /// Note: Jump destinations are 0-indexed. + fn exec_jump(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::MID)?; + + let index = self.stack.pop_usize()?; + jump(ref self, index) + } + + /// 0x57 - JUMPI operation. + /// Change the pc counter under a provided certain condition. + /// The new pc target has to be a JUMPDEST opcode. + /// # Specification: https://www.evm.codes/#57?fork=shanghai + fn exec_jumpi(ref self: VM) -> Result<(), EVMError> { + let index = self + .stack + .pop_saturating_usize()?; // Saturate because if b is 0, we skip the jump but don't want to fail here. + let b = self.stack.pop()?; + + self.charge_gas(gas::HIGH)?; + + if b != 0x0 { + jump(ref self, index)?; + } else { + // Return with a PC incremented by one - as JUMP and JUMPi increments + // are skipped in the main `execute_code` loop + self.set_pc(self.pc() + 1); + } + + Result::Ok(()) + } + + /// 0x58 - PC operation + /// Get the value of the program counter prior to the increment. + /// # Specification: https://www.evm.codes/#58?fork=shanghai + fn exec_pc(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + let pc = self.pc().into(); + self.stack.push(pc) + } + + /// 0x59 - MSIZE operation. + /// Get the value of memory size. + /// # Specification: https://www.evm.codes/#59?fork=shanghai + fn exec_msize(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + + let msize: u256 = self.memory.size().into(); + self.stack.push(msize) + } + + + /// 0x5A - GAS operation + /// Get the amount of available gas, including the corresponding reduction for the cost of this + /// instruction. + /// # Specification: https://www.evm.codes/#5a?fork=shanghai + fn exec_gas(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + self.stack.push(self.gas_left().into()) + } + + + /// 0x5b - JUMPDEST operation + /// Serves as a check that JUMP or JUMPI was executed correctly. + /// # Specification: https://www.evm.codes/#5b?fork=shanghai + /// + /// This doesn't have any affect on execution state, so we don't have + /// to do anything here. It's a NO-OP. + fn exec_jumpdest(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::JUMPDEST)?; + Result::Ok(()) + } + + /// 0x5c - TLOAD operation. + /// Load a word from the transient storage. + /// # Specification: https://www.evm.codes/#5c?fork=cancun + fn exec_tload(ref self: VM) -> Result<(), EVMError> { + let key = self.stack.pop()?; + let evm_address = self.message().target.evm; + + self.charge_gas(gas::WARM_ACCESS_COST)?; + + let value = self.env.state.read_transient_storage(evm_address, key); + self.stack.push(value) + } + + /// 0x5d - TSTORE operation. + /// Save a word to the transient storage. + /// # Specification: https://www.evm.codes/#5d?fork=cancun + fn exec_tstore(ref self: VM) -> Result<(), EVMError> { + let key = self.stack.pop()?; + let value = self.stack.pop()?; + + self.charge_gas(gas::WARM_ACCESS_COST)?; + + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + self.env.state.write_transient_storage(self.message().target.evm, key, value); + + return Result::Ok(()); + } + + /// 0x5e - MCOPY operation. + /// Copy memory from one location to another. + /// # Specification: https://www.evm.codes/#5e?fork=cancun + fn exec_mcopy(ref self: VM) -> Result<(), EVMError> { + let dest_offset = self.stack.pop_saturating_usize()?; + let source_offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + let words_size = bytes_32_words_size(size).into(); + let copy_gas_cost = gas::COPY * words_size; + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(max(dest_offset, source_offset), size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + let total_cost = gas::VERYLOW + .checked_add(copy_gas_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + if size == 0 { + return Result::Ok(()); + } + + self.memory.copy(size, source_offset, dest_offset); + Result::Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use core::cmp::max; + use core::num::traits::Bounded; + use core::result::ResultTrait; + use crate::errors::EVMError; + use crate::gas; + use crate::instructions::MemoryOperationTrait; + use crate::memory::MemoryTrait; + use crate::model::Address; + use crate::model::vm::VMTrait; + use crate::model::{Account, AccountTrait}; + use crate::stack::StackTrait; + use crate::state::StateTrait; + use crate::test_utils::{ + VMBuilderTrait, MemoryTestUtilsTrait, setup_test_environment, uninitialized_account, + native_token + }; + use snforge_std::{test_address, start_mock_call}; + use utils::helpers::compute_starknet_address; + use utils::traits::bytes::U8SpanExTrait; + + #[test] + fn test_pc_basic() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_pc().expect('exec_pc failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == 0, 'PC should be 0'); + } + + + #[test] + fn test_pc_gets_updated_properly_1() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.set_pc(9000); + vm.exec_pc().expect('exec_pc failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == 9000, 'updating PC failed'); + } + + // 0x51 - MLOAD + + #[test] + fn test_exec_mload_should_load_a_value_from_memory() { + assert_mload(0x1, 0, 0x1, 32); + } + + #[test] + fn test_exec_mload_should_load_a_value_from_memory_with_memory_expansion() { + assert_mload(0x1, 16, 0x100000000000000000000000000000000, 64); + } + + #[test] + fn test_exec_mload_should_load_a_value_from_memory_with_offset_larger_than_msize() { + assert_mload(0x1, 684, 0x0, 736); + } + + fn assert_mload(value: u256, offset: u256, expected_value: u256, expected_memory_size: u32) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.memory.store_with_expansion(value, 0); + + vm.stack.push(offset).expect('push failed'); + + // When + vm.exec_mload().expect('exec_mload failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == expected_value, 'mload failed'); + assert(vm.memory.size() == expected_memory_size, 'memory size error'); + } + + #[test] + fn test_exec_pop_should_pop_an_item_from_stack() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x01).expect('push failed'); + vm.stack.push(0x02).expect('push failed'); + + // When + let result = vm.exec_pop(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0x01, 'stack peek should return 0x01'); + } + + #[test] + fn test_exec_pop_should_stack_underflow() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + let result = vm.exec_pop(); + + // Then + assert(result.is_err(), 'should return Err '); + assert!(result.unwrap_err() == EVMError::StackUnderflow, "should return StackUnderflow"); + } + + #[test] + fn test_exec_mstore_should_store_max_uint256_offset_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + let result = vm.exec_mstore(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.memory.size() == 32, 'memory should be 32 bytes long'); + let stored = vm.memory.load(0); + assert(stored == Bounded::::MAX, 'should have stored max_uint256'); + } + + #[test] + fn test_exec_mstore_should_store_max_uint256_offset_1() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(Bounded::::MAX).expect('push failed'); + vm.stack.push(0x01).expect('push failed'); + + // When + let result = vm.exec_mstore(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.memory.size() == 64, 'memory should be 64 bytes long'); + let stored = vm.memory.load(1); + assert(stored == Bounded::::MAX, 'should have stored max_uint256'); + } + + #[test] + fn test_exec_mstore8_should_store_uint8_offset_31() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0xAB).expect('push failed'); + vm.stack.push(31).expect('push failed'); + + // When + let result = vm.exec_mstore8(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.memory.size() == 32, 'memory should be 32 bytes long'); + let stored = vm.memory.load(0); + assert(stored == 0xAB, 'mstore8 failed'); + } + + #[test] + fn test_exec_mstore8_should_store_uint8_offset_30() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0xAB).expect('push failed'); + vm.stack.push(30).expect('push failed'); + + // When + let result = vm.exec_mstore8(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.memory.size() == 32, 'memory should be 32 bytes long'); + let stored = vm.memory.load(0); + assert(stored == 0xAB00, 'mstore8 failed'); + } + + #[test] + fn test_exec_mstore8_should_store_uint8_offset_31_then_uint8_offset_30() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0xAB).expect('push failed'); + vm.stack.push(30).expect('push failed'); + vm.stack.push(0xCD).expect('push failed'); + vm.stack.push(31).expect('push failed'); + + // When + let result1 = vm.exec_mstore8(); + let result2 = vm.exec_mstore8(); + + // Then + assert(result1.is_ok() && result2.is_ok(), 'should have succeeded'); + assert(vm.memory.size() == 32, 'memory should be 32 bytes long'); + let stored = vm.memory.load(0); + assert(stored == 0xABCD, 'mstore8 failed'); + } + + #[test] + fn test_exec_mstore8_should_store_last_uint8_offset_31() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x123456789ABCDEF).expect('push failed'); + vm.stack.push(31).expect('push failed'); + + // When + let result = vm.exec_mstore8(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.memory.size() == 32, 'memory should be 32 bytes long'); + let stored = vm.memory.load(0); + assert(stored == 0xEF, 'mstore8 failed'); + } + + + #[test] + fn test_exec_mstore8_should_store_last_uint8_offset_63() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x123456789ABCDEF).expect('push failed'); + vm.stack.push(63).expect('push failed'); + + // When + let result = vm.exec_mstore8(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.memory.size() == 64, 'memory should be 64 bytes long'); + let stored = vm.memory.load(32); + assert(stored == 0xEF, 'mstore8 failed'); + } + + #[test] + fn test_msize_initial() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + let result = vm.exec_msize(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == 0, 'initial memory size should be 0'); + } + + #[test] + fn test_exec_msize_should_return_size_of_memory() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.memory.store_with_expansion(Bounded::::MAX, 0x00); + + // When + let result = vm.exec_msize(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == 32, 'should 32 bytes after MSIZE'); + } + + #[test] + fn test_exec_jump_valid() { + // Given + let bytecode: Span = [0x01, 0x02, 0x03, 0x5B, 0x04, 0x05].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let counter = 0x03; + vm.stack.push(counter).expect('push failed'); + + // When + vm.exec_jump().expect('exec_jump failed'); + + // Then + let pc = vm.pc(); + assert(pc == 0x03, 'PC should be JUMPDEST'); + } + + + #[test] + fn test_exec_jump_invalid() { + // Given + let bytecode: Span = [0x01, 0x02, 0x03, 0x5B, 0x04, 0x05].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let counter = 0x02; + vm.stack.push(counter).expect('push failed'); + + // When + let result = vm.exec_jump(); + + // Then + assert(result.is_err(), 'invalid jump dest'); + assert(result.unwrap_err() == EVMError::InvalidJump, 'invalid jump dest'); + } + + #[test] + fn test_exec_jump_out_of_bounds() { + // Given + let bytecode: Span = [0x01, 0x02, 0x03, 0x5B, 0x04, 0x05].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let counter = 0xFF; + vm.stack.push(counter).expect('push failed'); + + // When + let result = vm.exec_jump(); + + // Then + assert(result.is_err(), 'invalid jump dest'); + assert(result.unwrap_err() == EVMError::InvalidJump, 'invalid jump dest'); + } + + // TODO: This is third edge case in which `0x5B` is part of PUSHN instruction and hence + // not a valid opcode to jump to + #[test] + fn test_exec_jump_inside_pushn() { + // Given + let bytecode: Span = [0x60, 0x5B, 0x60, 0x00].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let counter = 0x01; + vm.stack.push(counter).expect('push failed'); + + // When + let result = vm.exec_jump(); + + // Then + assert(result.is_err(), 'exec_jump should throw error'); + assert(result.unwrap_err() == EVMError::InvalidJump, 'jump dest should be invalid'); + } + + #[test] + fn test_exec_jumpi_valid_non_zero_1() { + // Given + let bytecode: Span = [0x01, 0x02, 0x03, 0x5B, 0x04, 0x05].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let b = 0x1; + vm.stack.push(b).expect('push failed'); + let counter = 0x03; + vm.stack.push(counter).expect('push failed'); + + // When + vm.exec_jumpi().expect('exec_jumpi failed'); + + // Then + let pc = vm.pc(); + assert(pc == 0x03, 'PC should be JUMPDEST'); + } + + #[test] + fn test_exec_jumpi_valid_non_zero_2() { + // Given + let bytecode: Span = [0x01, 0x02, 0x03, 0x5B, 0x04, 0x05].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let b = 0x69; + vm.stack.push(b).expect('push failed'); + let counter = 0x03; + vm.stack.push(counter).expect('push failed'); + + // When + vm.exec_jumpi().expect('exec_jumpi failed'); + + // Then + let pc = vm.pc(); + assert(pc == 0x03, 'PC should be JUMPDEST'); + } + + #[test] + fn test_exec_jumpi_valid_zero() { + // Given + let bytecode: Span = [0x01, 0x02, 0x03, 0x5B, 0x04, 0x05].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let b = 0x0; + vm.stack.push(b).expect('push failed'); + let counter = 0x03; + vm.stack.push(counter).expect('push failed'); + let old_pc = vm.pc(); + + // When + vm.exec_jumpi().expect('exec_jumpi failed'); + + // Then + let pc = vm.pc(); + // If the jump is not taken, the PC should be incremented by 1 + assert_eq!(pc, old_pc + 1); + } + + #[test] + fn test_exec_jumpi_invalid_non_zero() { + // Given + let bytecode: Span = [0x60, 0x5B, 0x60, 0x00].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let b = 0x69; + vm.stack.push(b).expect('push failed'); + let counter = 0x69; + vm.stack.push(counter).expect('push failed'); + + // When + let result = vm.exec_jumpi(); + + // Then + assert(result.is_err(), 'invalid jump dest'); + assert(result.unwrap_err() == EVMError::InvalidJump, 'invalid jump dest'); + } + + + #[test] + fn test_exec_jumpi_invalid_zero() { + // Given + let bytecode: Span = [0x01, 0x02, 0x03, 0x5B, 0x04, 0x05].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let b = 0x0; + vm.stack.push(b).expect('push failed'); + let counter = 0x69; + vm.stack.push(counter).expect('push failed'); + let old_pc = vm.pc(); + + // When + vm.exec_jumpi().expect('exec_jumpi failed'); + + // Then + let pc = vm.pc(); + // If the jump is not taken, the PC should be incremented by 1 + assert_eq!(pc, old_pc + 1); + } + + #[test] + #[should_panic(expected: ('exec_jump should throw error',))] + fn test_exec_jumpi_inside_pushn() { + // Given + let bytecode: Span = [0x60, 0x5B, 0x60, 0x00].span(); + + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + + let b = 0x00; + vm.stack.push(b).expect('push failed'); + let counter = 0x01; + vm.stack.push(counter).expect('push failed'); + + // When + let result = vm.exec_jumpi(); + + // Then + assert(result.is_err(), 'exec_jump should throw error'); + assert(result.unwrap_err() == EVMError::InvalidJump, 'jump dest should be invalid'); + } + + #[test] + fn test_exec_sload_should_push_value_on_stack() { + // Given + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().build(); + let evm_address = vm.message().target.evm; + + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0x02; + vm.env.state.write_state(evm_address, key, value); + vm.stack.push(key.into()).expect('push failed'); + + // When + let result = vm.exec_sload(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == value, 'sload failed'); + } + + #[test] + fn test_exec_sstore_on_account_in_st() { + // Given + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().build(); + let test_address = test_address(); + let evm_address = vm.message().target.evm; + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); + let bytecode = [0xab, 0xcd, 0xef].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, + code: bytecode, + code_hash: code_hash, + nonce: 1, + balance: 0, + selfdestruct: false, + is_created: false, + }; + vm.env.state.set_account(account); + + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + + vm.stack.push(value).expect('push failed'); + vm.stack.push(key).expect('push failed'); + + // When + start_mock_call::(starknet_address, selector!("storage"), 0); + let result = vm.exec_sstore(); + + // Then + assert(result.is_ok(), 'exec sstore failed'); + assert(vm.env.state.read_state(evm_address, key) == value, 'wrong value in state'); + } + + #[test] + fn test_exec_sstore_on_account_undeployed() { + // Given + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().build(); + let evm_address = vm.message().target.evm; + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + + vm.stack.push(value).expect('push failed'); + vm.stack.push(key).expect('push failed'); + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 0); + let result = vm.exec_sstore(); + + // Then + assert(result.is_ok(), 'exec sstore failed'); + assert(vm.env.state.read_state(evm_address, key) == value, 'wrong value in state'); + } + + #[test] + fn test_exec_sstore_on_contract_account_alive() { + // Given + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().build(); + let test_address = test_address(); + let evm_address = vm.message().target.evm; + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); + let bytecode = [0xab, 0xcd, 0xef].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, + code: bytecode, + code_hash: code_hash, + nonce: 1, + balance: 0, + selfdestruct: false, + is_created: false, + }; + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + + vm.stack.push(value).expect('push failed'); + vm.stack.push(key).expect('push failed'); + + vm.env.state.set_account(account); + assert!(vm.env.state.is_account_alive(account.address.evm)); + + // When + start_mock_call::(account.starknet_address(), selector!("storage"), 0); + let result = vm.exec_sstore(); + + // Then + assert(result.is_ok(), 'exec sstore failed'); + assert(vm.env.state.read_state(evm_address, key) == value, 'wrong value in state'); + } + + #[test] + fn test_exec_sstore_should_fail_static_call() { + // Given + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().with_read_only().build(); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); + vm.stack.push(key).expect('push failed'); + + // When + start_mock_call::(vm.message().target.starknet, selector!("storage"), 0); + start_mock_call::(native_token(), selector!("balanceOf"), 0); + let result = vm.exec_sstore(); + + // Then + assert(result.is_err(), 'should have errored'); + assert(result.unwrap_err() == EVMError::WriteInStaticContext, 'wrong error returned'); + } + + #[test] + fn test_exec_sstore_should_fail_gas_left_inf_call_stipend_eip_1706() { + // Given + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().with_gas_left(gas::CALL_STIPEND).build(); + let test_address = test_address(); + let evm_address = vm.message().target.evm; + let starknet_address = compute_starknet_address( + test_address, evm_address, uninitialized_account() + ); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); + vm.stack.push(key).expect('push failed'); + + // When + start_mock_call::(starknet_address, selector!("storage"), 0); + start_mock_call::(native_token(), selector!("balanceOf"), 0); + let result = vm.exec_sstore(); + + // Then + assert(result.is_err(), 'should have errored'); + assert(result.unwrap_err() == EVMError::OutOfGas, 'wrong error returned'); + } + + + #[test] + fn test_gas_should_push_gas_left_to_stack() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // When + vm.exec_gas().unwrap(); + + // Then + let result = vm.stack.peek().unwrap(); + assert(result == vm.gas_left().into(), 'stack top should be gas_limit'); + } + + #[test] + fn test_tload_should_load_a_value_from_transient_storage() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.env.state.write_transient_storage(vm.message().target.evm, key, value); + vm.stack.push(key.into()).expect('push failed'); + + // When + let gas_before = vm.gas_left(); + vm.exec_tload().expect('exec_tload failed'); + let gas_after = vm.gas_left(); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == value, 'tload failed'); + assert(gas_before - gas_after == gas::WARM_ACCESS_COST, 'gas charged error'); + } + + #[test] + fn test_tstore_should_fail_staticcall() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_read_only().build(); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); + vm.stack.push(key.into()).expect('push failed'); + + // When + let gas_before = vm.gas_left(); + let result = vm.exec_tstore(); + let gas_after = vm.gas_left(); + + // Then + assert(result.is_err(), 'should have errored'); + assert(result.unwrap_err() == EVMError::WriteInStaticContext, 'wrong error returned'); + assert(gas_before - gas_after == gas::WARM_ACCESS_COST, 'gas charged error'); + } + + #[test] + fn test_tstore_should_store_a_value_to_transient_storage() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); + vm.stack.push(key.into()).expect('push failed'); + + // When + let gas_before = vm.gas_left(); + vm.exec_tstore().expect('exec_tstore failed'); + let gas_after = vm.gas_left(); + + // Then + assert( + vm.env.state.read_transient_storage(vm.message().target.evm, key) == value, + 'tstore failed' + ); + assert(gas_before - gas_after == gas::WARM_ACCESS_COST, 'gas charged error'); + } + + #[test] + fn test_exec_mcopy_should_copy_two_words_at_destination_offset() { + let values: Array = array![0xFF, 0xEE]; + assert_mcopy(0x00, 0x80, 0x40, values); + } + + #[test] + fn test_exec_mcopy_should_copy_two_words_at_destination_offset_with_overlap() { + let values: Array = array![0xFF, 0xEE]; + assert_mcopy(0x00, 0x20, 0x40, values); + } + + fn assert_mcopy(source_offset: u32, dest_offset: u32, size: u32, values: Array) { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let mut i = 0; + for element in values + .span() { + vm.memory.store_with_expansion((*element).into(), source_offset + 0x20 * i); + i += 1; + }; + vm.stack.push(size.into()).expect('push failed'); + vm.stack.push(source_offset.into()).expect('push failed'); + vm.stack.push(dest_offset.into()).expect('push failed'); + + let words_size = ((size + 31) / 32).into(); + let copy_gas_cost = gas::COPY * words_size; + + // When + let expected_gas = gas::VERYLOW + + gas::memory_expansion( + vm.memory.size(), [(max(dest_offset, source_offset), size)].span() + ) + .unwrap() + .expansion_cost + + copy_gas_cost; + let gas_before = vm.gas_left(); + let result = vm.exec_mcopy(); + let gas_after = vm.gas_left(); + + // Then + assert(result.is_ok(), 'should have succeeded'); + assert(vm.stack.is_empty(), 'stack should be empty'); + assert(vm.memory.size() == dest_offset + size, 'memory size error'); + i = 0; + for element in values + .span() { + let stored_word = vm.memory.load(dest_offset + 0x20 * i); + assert(stored_word == (*element).into(), 'mcopy failed'); + i += 1; + }; + assert(gas_before - gas_after == expected_gas, 'gas error'); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/push_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/push_operations.cairo new file mode 100644 index 000000000..d6bd057cb --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/push_operations.cairo @@ -0,0 +1,751 @@ +//! Push Operations. + +use crate::errors::EVMError; +use crate::gas; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; +use utils::traits::bytes::{FromBytes, U8SpanExTrait}; + +/// Place i bytes items on stack. +#[inline(always)] +fn exec_push_i(ref self: VM, i: u8) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let i = i.into(); + let args_pc = self.pc() + 1; // PUSH_N Arguments begin at pc + 1 + let data = self.read_code(args_pc, i); + + self.set_pc(self.pc() + i); + + if data.len() == 0 { + self.stack.push(0) + } else { + self.stack.push(data.pad_right_with_zeroes(i).from_be_bytes_partial().unwrap()) + } +} + +#[generate_trait] +pub impl PushOperations of PushOperationsTrait { + /// 5F - PUSH0 operation + /// # Specification: https://www.evm.codes/#5f?fork=shanghai + #[inline(always)] + fn exec_push0(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::BASE)?; + self.stack.push(0) + } + + + /// 0x60 - PUSH1 operation + /// # Specification: https://www.evm.codes/#60?fork=shanghai + #[inline(always)] + fn exec_push1(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 1) + } + + + /// 0x61 - PUSH2 operation + /// # Specification: https://www.evm.codes/#61?fork=shanghai + #[inline(always)] + fn exec_push2(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 2) + } + + + /// 0x62 - PUSH3 operation + /// # Specification: https://www.evm.codes/#62?fork=shanghai + #[inline(always)] + fn exec_push3(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 3) + } + + /// 0x63 - PUSH4 operation + /// # Specification: https://www.evm.codes/#63?fork=shanghai + #[inline(always)] + fn exec_push4(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 4) + } + + /// 0x64 - PUSH5 operation + /// # Specification: https://www.evm.codes/#64?fork=shanghai + #[inline(always)] + fn exec_push5(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 5) + } + + /// 0x65 - PUSH6 operation + /// # Specification: https://www.evm.codes/#65?fork=shanghai + #[inline(always)] + fn exec_push6(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 6) + } + + /// 0x66 - PUSH7 operation + /// # Specification: https://www.evm.codes/#66?fork=shanghai + #[inline(always)] + fn exec_push7(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 7) + } + + /// 0x67 - PUSH8 operation + /// # Specification: https://www.evm.codes/#67?fork=shanghai + #[inline(always)] + fn exec_push8(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 8) + } + + + /// 0x68 - PUSH9 operation + /// # Specification: https://www.evm.codes/#68?fork=shanghai + #[inline(always)] + fn exec_push9(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 9) + } + + /// 0x69 - PUSH10 operation + /// # Specification: https://www.evm.codes/#69?fork=shanghai + #[inline(always)] + fn exec_push10(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 10) + } + + /// 0x6A - PUSH11 operation + /// # Specification: https://www.evm.codes/#6a?fork=shanghai + #[inline(always)] + fn exec_push11(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 11) + } + + /// 0x6B - PUSH12 operation + /// # Specification: https://www.evm.codes/#6b?fork=shanghai + #[inline(always)] + fn exec_push12(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 12) + } + + + /// 0x6C - PUSH13 operation + /// # Specification: https://www.evm.codes/#6c?fork=shanghai + #[inline(always)] + fn exec_push13(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 13) + } + + /// 0x6D - PUSH14 operation + /// # Specification: https://www.evm.codes/#6d?fork=shanghai + #[inline(always)] + fn exec_push14(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 14) + } + + + /// 0x6E - PUSH15 operation + /// # Specification: https://www.evm.codes/#6e?fork=shanghai + #[inline(always)] + fn exec_push15(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 15) + } + + /// 0x6F - PUSH16 operation + /// # Specification: https://www.evm.codes/#6f?fork=shanghai + #[inline(always)] + fn exec_push16(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 16) + } + + /// 0x70 - PUSH17 operation + /// # Specification: https://www.evm.codes/#70?fork=shanghai + #[inline(always)] + fn exec_push17(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 17) + } + + /// 0x71 - PUSH18 operation + /// # Specification: https://www.evm.codes/#71?fork=shanghai + #[inline(always)] + fn exec_push18(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 18) + } + + + /// 0x72 - PUSH19 operation + /// # Specification: https://www.evm.codes/#72?fork=shanghai + #[inline(always)] + fn exec_push19(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 19) + } + + /// 0x73 - PUSH20 operation + /// # Specification: https://www.evm.codes/#73?fork=shanghai + #[inline(always)] + fn exec_push20(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 20) + } + + + /// 0x74 - PUSH21 operation + /// # Specification: https://www.evm.codes/#74?fork=shanghai + #[inline(always)] + fn exec_push21(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 21) + } + + + /// 0x75 - PUSH22 operation + /// # Specification: https://www.evm.codes/#75?fork=shanghai + #[inline(always)] + fn exec_push22(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 22) + } + + + /// 0x76 - PUSH23 operation + /// # Specification: https://www.evm.codes/#76?fork=shanghai + #[inline(always)] + fn exec_push23(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 23) + } + + + /// 0x77 - PUSH24 operation + /// # Specification: https://www.evm.codes/#77?fork=shanghai + #[inline(always)] + fn exec_push24(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 24) + } + + + /// 0x78 - PUSH21 operation + /// # Specification: https://www.evm.codes/#78?fork=shanghai + #[inline(always)] + fn exec_push25(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 25) + } + + + /// 0x79 - PUSH26 operation + /// # Specification: https://www.evm.codes/#79?fork=shanghai + #[inline(always)] + fn exec_push26(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 26) + } + + + /// 0x7A - PUSH27 operation + /// # Specification: https://www.evm.codes/#7a?fork=shanghai + #[inline(always)] + fn exec_push27(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 27) + } + + /// 0x7B - PUSH28 operation + /// # Specification: https://www.evm.codes/#7b?fork=shanghai + #[inline(always)] + fn exec_push28(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 28) + } + + + /// 0x7C - PUSH29 operation + /// # Specification: https://www.evm.codes/#7c?fork=shanghai + #[inline(always)] + fn exec_push29(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 29) + } + + + /// 0x7D - PUSH30 operation + /// # Specification: https://www.evm.codes/#7d?fork=shanghai + #[inline(always)] + fn exec_push30(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 30) + } + + + /// 0x7E - PUSH31 operation + /// # Specification: https://www.evm.codes/#7e?fork=shanghai + #[inline(always)] + fn exec_push31(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 31) + } + + + /// 0x7F - PUSH32 operation + /// # Specification: https://www.evm.codes/#7f?fork=shanghai + #[inline(always)] + fn exec_push32(ref self: VM) -> Result<(), EVMError> { + exec_push_i(ref self, 32) + } +} + +#[cfg(test)] +mod tests { + use crate::gas; + use crate::instructions::PushOperationsTrait; + use crate::model::vm::VMTrait; + use crate::stack::StackTrait; + use crate::test_utils::{VMBuilderTrait}; + use super::exec_push_i; + + #[test] + fn test_exec_push_i() { + // Test cases: (bytes_to_push, bytecode_length, expected_value) + let test_cases: Span<(u8, u8, u256)> = [ + (1, 1, 0xFF), // Push 1 byte + (16, 16, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF), // Push 16 bytes + ( + 32, 32, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ), // Push 32 bytes (max) + ( + 32, 16, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000 + ), // Push 32 bytes, but only 16 available + (1, 0, 0), // Push 1 byte, but no bytes available + ].span(); + + for elem in test_cases { + let (bytes_to_push, bytecode_length, expected_value) = *elem; + + let mut vm = VMBuilderTrait::new_with_presets() + .with_bytecode(get_n_0xFF(bytecode_length)) + .build(); + let initial_pc = vm.pc(); + let initial_gas = vm.gas_left(); + + // Execute exec_push_i + let result = exec_push_i(ref vm, bytes_to_push); + + assert!(result.is_ok()); + + // Check stack + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), expected_value); + + // Check PC increment + let pc_increment = vm.pc() - initial_pc; + assert_eq!(pc_increment, bytes_to_push.into()); + + // Check gas consumption + let gas_consumed = initial_gas - vm.gas_left(); + assert_eq!(gas_consumed, gas::VERYLOW); + } + } + + /// Get a span of 0xFF bytes of length n preceded by a 0x00 byte + /// to account for the argument offset in push operations + fn get_n_0xFF(mut n: u8) -> Span { + let mut array: Array = ArrayTrait::new(); + array.append(0x00); + for _ in 0..n { + array.append(0xFF); + }; + array.span() + } + + #[test] + fn test_push0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(0)).build(); + + // When + vm.exec_push0().expect('exec_push0 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0); + } + + #[test] + fn test_push1() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(1)).build(); + + // When + vm.exec_push1().expect('exec_push1 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFF); + } + + #[test] + fn test_push2() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(2)).build(); + + // When + vm.exec_push2().expect('exec_push2 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFF); + } + + #[test] + fn test_push3() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(3)).build(); + + // When + vm.exec_push3().expect('exec_push3 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFF); + } + + #[test] + fn test_push4() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(4)).build(); + + // When + vm.exec_push4().expect('exec_push4 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFF); + } + + #[test] + fn test_push5() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(5)).build(); + + // When + vm.exec_push5().expect('exec_push5 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFF); + } + + #[test] + fn test_push6() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(6)).build(); + + // When + vm.exec_push6().expect('exec_push6 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFF); + } + + #[test] + fn test_push7() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(7)).build(); + + // When + vm.exec_push7().expect('exec_push7 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFF); + } + + + #[test] + fn test_push8() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(8)).build(); + + // When + vm.exec_push8().expect('exec_push8 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push9() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(9)).build(); + + // When + vm.exec_push9().expect('exec_push9 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push10() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(10)).build(); + + // When + vm.exec_push10().expect('exec_push10 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push11() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(11)).build(); + + // When + vm.exec_push11().expect('exec_push11 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push12() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(12)).build(); + + // When + vm.exec_push12().expect('exec_push12 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push13() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(13)).build(); + + // When + vm.exec_push13().expect('exec_push13 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push14() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(14)).build(); + + // When + vm.exec_push14().expect('exec_push14 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push15() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(15)).build(); + + // When + vm.exec_push15().expect('exec_push15 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push16() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(16)).build(); + + // When + vm.exec_push16().expect('exec_push16 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push17() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(17)).build(); + + // When + vm.exec_push17().expect('exec_push17 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push18() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(18)).build(); + + // When + vm.exec_push18().expect('exec_push18 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + #[test] + fn test_push19() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(19)).build(); + + // When + vm.exec_push19().expect('exec_push19 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push20() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(20)).build(); + + // When + vm.exec_push20().expect('exec_push20 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push21() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(21)).build(); + + // When + vm.exec_push21().expect('exec_push21 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push22() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(22)).build(); + + // When + vm.exec_push22().expect('exec_push22 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + #[test] + fn test_push23() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(23)).build(); + + // When + vm.exec_push23().expect('exec_push23 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + #[test] + fn test_push24() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(24)).build(); + + // When + vm.exec_push24().expect('exec_push24 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push25() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(25)).build(); + + // When + vm.exec_push25().expect('exec_push25 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!(vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + } + + #[test] + fn test_push26() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(26)).build(); + + // When + vm.exec_push26().expect('exec_push26 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!( + vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn test_push27() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(27)).build(); + + // When + vm.exec_push27().expect('exec_push27 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!( + vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn test_push28() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(28)).build(); + + // When + vm.exec_push28().expect('exec_push28 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!( + vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn test_push29() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(29)).build(); + + // When + vm.exec_push29().expect('exec_push29 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!( + vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn test_push30() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(30)).build(); + + // When + vm.exec_push30().expect('exec_push30 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!( + vm.stack.peek().unwrap(), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn test_push31() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(31)).build(); + + // When + vm.exec_push31().expect('exec_push31 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!( + vm.stack.peek().unwrap(), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + } + + #[test] + fn test_push32() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(get_n_0xFF(32)).build(); + + // When + vm.exec_push32().expect('exec_push32 failed'); + // Then + assert_eq!(vm.stack.len(), 1); + assert_eq!( + vm.stack.peek().unwrap(), + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/sha3.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/sha3.cairo new file mode 100644 index 000000000..73b3d99b9 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/sha3.cairo @@ -0,0 +1,607 @@ +use core::cmp::min; +//! SHA3. +use core::keccak::{cairo_keccak}; +use core::num::traits::CheckedAdd; + +// Internal imports +use crate::errors::EVMError; +use crate::gas; +use crate::memory::MemoryTrait; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; +use utils::helpers::bytes_32_words_size; +use utils::traits::array::ArrayExtTrait; +use utils::traits::integer::U256Trait; +#[generate_trait] +pub impl Sha3Impl of Sha3Trait { + /// SHA3 operation : Hashes n bytes in memory at a given offset in memory + /// and push the hash result to the stack. + /// + /// # Inputs + /// * `offset` - The offset in memory where to read the data + /// * `size` - The amount of bytes to read + /// + /// # Specification: https://www.evm.codes/#20?fork=shanghai + fn exec_sha3(ref self: VM) -> Result<(), EVMError> { + let offset: usize = self.stack.pop_saturating_usize()?; + let mut size: usize = self + .stack + .pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + let words_size = bytes_32_words_size(size).into(); + let word_gas_cost = gas::KECCAK256WORD * words_size; + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + let total_cost = gas::KECCAK256 + .checked_add(word_gas_cost) + .ok_or(EVMError::OutOfGas)? + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let mut to_hash: Array = Default::default(); + + let (nb_words, nb_zeroes) = compute_memory_words_amount(size, offset, self.memory.size()); + let mut last_input_offset = fill_array_with_memory_words( + ref self, ref to_hash, offset, nb_words + ); + // Fill array to hash with zeroes for bytes out of memory bound + // which is faster than reading them from memory + to_hash.append_n(0, 4 * nb_zeroes); + + // For cases where the size of bytes to hash isn't a multiple of 8, + // prepare the last bytes to hash into last_input instead of appending + // it to to_hash. + let last_input: u64 = if (size % 32 != 0) { + let loaded = self.memory.load(last_input_offset); + prepare_last_input(ref to_hash, loaded, size % 32) + } else { + 0 + }; + // Properly set the memory length in case we skipped reading zeroes + self.memory.ensure_length(size + offset); + let mut hash = cairo_keccak(ref to_hash, last_input, size % 8); + self.stack.push(hash.reverse_endianness()) + } +} + + +/// Computes how many words are read from the memory +/// and how many words must be filled with zeroes +/// given a target size, a memory offset and the length of the memory. +/// +/// # Arguments +/// +/// * `size` - The amount of bytes to hash +/// * `offset` - Offset in memory +/// * `mem_len` - Size of the memory +/// Returns : (nb_words, nb_zeroes) +fn compute_memory_words_amount(size: u32, offset: u32, mem_len: u32) -> (u32, u32) { + // Bytes to hash are less than a word size + if size < 32 { + return (0, 0); + } + // Bytes out of memory bound are zeroes + if offset > mem_len { + return (0, size / 32); + } + // The only word to read from memory is less than 32 bytes + if mem_len - offset < 32 { + return (1, (size / 32) - 1); + } + + let bytes_to_read = min(mem_len - offset, size); + let nb_words = bytes_to_read / 32; + (nb_words, (size / 32) - nb_words) +} + +/// Fills the `to_hash` array with little endian u64s +/// by splitting words read from the memory and +/// returns the next offset to read from. +/// +/// # Arguments +/// +/// * `self` - The context in which the memory is read +/// * `to_hash` - A reference to the array to fill +/// * `offset` - Offset in memory to start reading from +/// * `amount` - The amount of words to read from memory +/// Return the new offset +fn fill_array_with_memory_words( + ref self: VM, ref to_hash: Array, mut offset: u32, mut amount: u32 +) -> u32 { + for _ in 0 + ..amount { + let loaded = self.memory.load(offset); + let ((high_h, low_h), (high_l, low_l)) = loaded.split_into_u64_le(); + to_hash.append(low_h); + to_hash.append(high_h); + to_hash.append(low_l); + to_hash.append(high_l); + + offset += 32; + }; + offset +} + +/// Fills the `to_hash` array with the n-1 remaining little endian u64 +/// depending on size from a word and returns +/// the u64 containing the last 8 bytes word to hash. +/// +/// # Arguments +/// +/// * `to_hash` - A reference to the array to fill +/// * `value` - The word to split in u64 words +/// * `size` - The amount of bytes still required to hash +/// Returns the last u64 word that isn't 8 Bytes long. +fn prepare_last_input(ref to_hash: Array, value: u256, size: u32) -> u64 { + let ((high_h, low_h), (high_l, low_l)) = value.split_into_u64_le(); + if size < 8 { + return low_h; + } else if size < 16 { + to_hash.append(low_h); + return high_h; + } else if size < 24 { + to_hash.append(low_h); + to_hash.append(high_h); + return low_l; + } else { + to_hash.append(low_h); + to_hash.append(high_h); + to_hash.append(low_l); + return high_l; + } +} + +#[cfg(test)] +mod tests { + use crate::instructions::Sha3Trait; + use crate::instructions::sha3; + use crate::memory::MemoryTrait; + use crate::stack::StackTrait; + use crate::test_utils::{VMBuilderTrait, MemoryTestUtilsTrait}; + + #[test] + fn test_exec_sha3_size_0_offset_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x00).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + + assert( + result == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, + 'wrong result' + ); + assert(vm.memory.size() == 32, 'wrong memory size'); + } + + + #[test] + fn test_exec_sha3_should_not_expand_memory() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x05).expect('push failed'); + vm.stack.push(0x04).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert_eq!(result, 0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec); + assert_eq!(vm.memory.size(), 32); + } + + #[test] + fn test_exec_sha3_should_expand_memory() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(24).expect('push failed'); + vm.stack.push(10).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert_eq!(result, 0x827b659bbda2a0bdecce2c91b8b68462545758f3eba2dbefef18e0daf84f5ccd); + assert_eq!(vm.memory.size(), 64); + } + + #[test] + fn test_exec_sha3_size_0xFFFFF_offset_1000() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0xFFFFF).expect('push failed'); + vm.stack.push(1000).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0xbe6f1b42b34644f918560a07f959d23e532dea5338e4b9f63db0caeb608018fa, + 'wrong result' + ); + assert(vm.memory.size() == (((0xFFFFF + 1000) + 31) / 32) * 32, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_1000000_offset_2() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(1000000).expect('push failed'); + vm.stack.push(2).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0x4aa461ae9513f3b03ae397740ade979809dd02ae2c14e101b32842fbee21f0a, + 'wrong result' + ); + assert(vm.memory.size() == (((1000000 + 2) + 31) / 32) * 32, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_1000000_offset_23() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(1000000).expect('push failed'); + vm.stack.push(2).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0x4aa461ae9513f3b03ae397740ade979809dd02ae2c14e101b32842fbee21f0a, + 'wrong result' + ); + assert(vm.memory.size() == (((1000000 + 23) + 31) / 32) * 32, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_1_offset_2048() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(1).expect('push failed'); + vm.stack.push(2048).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a, + 'wrong result' + ); + assert(vm.memory.size() == (((2048 + 1) + 31) / 32) * 32, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_0_offset_1024() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0).expect('push failed'); + vm.stack.push(1024).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, + 'wrong result' + ); + assert(vm.memory.size() == 1024, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_32_offset_2016() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(32).expect('push failed'); + vm.stack.push(2016).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563, + 'wrong result' + ); + assert(vm.memory.size() == (((2016 + 32) + 31) / 32) * 32, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_32_offset_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(32).expect('push failed'); + vm.stack.push(0).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0x567d6b045256961aee949d6bb4d5f814c5b42e6b8bb49a833e8e89fbcddee86c, + 'wrong result' + ); + assert(vm.memory.size() == 32, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_31_offset_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(31).expect('push failed'); + vm.stack.push(0).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0x4b13f212816c02cc818ba4802e81a4ac1904d2c920fe8d8cf3e4f05233a57d2e, + 'wrong result' + ); + assert(vm.memory.size() == 32, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_33_offset_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(33).expect('push failed'); + vm.stack.push(0).expect('push failed'); + + vm + .memory + .store_with_expansion( + 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000, 0 + ); + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0xa6fa3edfabbe64b6ce26120b21ac9b8191005115d5e7e03fa58ec9cc74c0f2f4, + 'wrong result' + ); + assert(vm.memory.size() == 64, 'wrong memory size'); + } + + #[test] + fn test_exec_sha3_size_0x0C80_offset_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x0C80).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + let mut mem_dst: u32 = 0; + while mem_dst <= 0x0C80 { + vm + .memory + .store_with_expansion( + 0xFAFAFAFA00000000000000000000000000000000000000000000000000000000, mem_dst + ); + mem_dst += 0x20; + }; + + // When + vm.exec_sha3().expect('exec_sha3 failed'); + + // Then + let result = vm.stack.peek().unwrap(); + assert( + result == 0x2022ae07f3a362b08ac0a4bcb785c830cb5c368dc0ce6972249c6abbc68a5291, + 'wrong result' + ); + assert(vm.memory.size() == 0x0C80 + 32, 'wrong memory size'); + } + + #[test] + fn test_internal_fill_array_with_memory_words() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let mut to_hash: Array = Default::default(); + + vm + .memory + .store_with_expansion( + 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000, 0 + ); + let mut size = 32; + let mut offset = 0; + + // When + let (words_from_mem, _) = sha3::compute_memory_words_amount(size, offset, vm.memory.size()); + sha3::fill_array_with_memory_words(ref vm, ref to_hash, offset, words_from_mem); + + // Then + assert(to_hash.len() == 4, 'wrong array length'); + assert((*to_hash[0]) == 0xE5000000FFFFFFFA, 'wrong array value'); + assert((*to_hash[1]) == 0xDE00000077000000, 'wrong array value'); + assert((*to_hash[2]) == 0x00200400000000AD, 'wrong array value'); + assert((*to_hash[3]) == 0x0000450000DEFA00, 'wrong array value'); + } + + #[test] + fn test_internal_fill_array_with_memory_words_size_33() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let mut to_hash: Array = Default::default(); + + vm + .memory + .store_with_expansion( + 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000, 0 + ); + let mut size = 33; + let mut offset = 0; + + // When + let (words_from_mem, _) = sha3::compute_memory_words_amount(size, offset, vm.memory.size()); + sha3::fill_array_with_memory_words(ref vm, ref to_hash, offset, words_from_mem); + + // Then + assert(to_hash.len() == 4, 'wrong array length'); + assert((*to_hash[0]) == 0xE5000000FFFFFFFA, 'wrong array value'); + assert((*to_hash[1]) == 0xDE00000077000000, 'wrong array value'); + assert((*to_hash[2]) == 0x00200400000000AD, 'wrong array value'); + assert((*to_hash[3]) == 0x0000450000DEFA00, 'wrong array value'); + } + + #[test] + fn test_internal_fill_array_with_last_inputs_size_5() { + // Given + let mut to_hash: Array = Default::default(); + let value: u256 = 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000; + let size = 5; + + // When + let result = sha3::prepare_last_input(ref to_hash, value, size); + + // Then + assert(result == 0xE5000000FFFFFFFA, 'wrong result'); + assert(to_hash.len() == 0, 'wrong result'); + } + + #[test] + fn test_internal_fill_array_with_last_inputs_size_20() { + // Given + let mut to_hash: Array = Default::default(); + let value: u256 = 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000; + let size = 20; + + // When + let result = sha3::prepare_last_input(ref to_hash, value, size); + + // Then + assert(result == 0x00200400000000AD, 'wrong result'); + assert(to_hash.len() == 2, 'wrong result'); + assert((*to_hash[0]) == 0xE5000000FFFFFFFA, 'wrong array value'); + assert((*to_hash[1]) == 0xDE00000077000000, 'wrong array value'); + } + + #[test] + fn test_internal_fill_array_with_last_inputs_size_50() { + // Given + let mut to_hash: Array = Default::default(); + let value: u256 = 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000; + let size = 50; + + // When + let result = sha3::prepare_last_input(ref to_hash, value, size); + + // Then + assert(result == 0x0000450000DEFA00, 'wrong result'); + assert(to_hash.len() == 3, 'wrong result'); + assert((*to_hash[0]) == 0xE5000000FFFFFFFA, 'wrong array value'); + assert((*to_hash[1]) == 0xDE00000077000000, 'wrong array value'); + assert((*to_hash[2]) == 0x00200400000000AD, 'wrong array value'); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/stop_and_arithmetic_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/stop_and_arithmetic_operations.cairo new file mode 100644 index 000000000..54b9da66b --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/stop_and_arithmetic_operations.cairo @@ -0,0 +1,744 @@ +//! Stop and Arithmetic Operations. +use core::integer::{u512_safe_div_rem_by_u256}; +use core::math::u256_mul_mod_n; +use core::num::traits::CheckedAdd; +use core::num::traits::{OverflowingAdd, OverflowingMul, OverflowingSub}; +use crate::errors::EVMError; +use crate::gas; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; +use utils::i256::i256; +use utils::math::{Exponentiation, WrappingExponentiation, u256_wide_add}; +use utils::traits::integer::BytesUsedTrait; + +#[generate_trait] +pub impl StopAndArithmeticOperations of StopAndArithmeticOperationsTrait { + /// 0x00 - STOP + /// Halts the execution of the current program. + /// # Specification: https://www.evm.codes/#00?fork=shanghai + fn exec_stop(ref self: VM) { + // return_data store the return_data for the last executed sub context + // see CALLs opcodes. When it runs the STOP opcode, it stops the current + // execution context with *no* return data (unlike RETURN and REVERT). + // hence it just clear the return_data and stop. + self.return_data = [].span(); + self.stop(); + } + + /// 0x01 - ADD + /// Addition operation + /// a + b: integer result of the addition modulo 2^256. + /// # Specification: https://www.evm.codes/#01?fork=shanghai + fn exec_add(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + + // Compute the addition + let (result, _) = (*popped[0]).overflowing_add(*popped[1]); + + self.stack.push(result) + } + + /// 0x02 - MUL + /// Multiplication + /// a * b: integer result of the multiplication modulo 2^256. + /// # Specification: https://www.evm.codes/#02?fork=shanghai + fn exec_mul(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::LOW)?; + let popped = self.stack.pop_n(2)?; + + // Compute the multiplication + let (result, _) = (*popped[0]).overflowing_mul(*popped[1]); + + self.stack.push(result) + } + + /// 0x03 - SUB + /// Subtraction operation + /// a - b: integer result of the subtraction modulo 2^256. + /// # Specification: https://www.evm.codes/#03?fork=shanghai + fn exec_sub(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::VERYLOW)?; + let popped = self.stack.pop_n(2)?; + + // Compute the subtraction + let (result, _) = (*popped[0]).overflowing_sub(*popped[1]); + + self.stack.push(result) + } + + /// 0x04 - DIV + /// If the denominator is 0, the result will be 0. + /// a / b: integer result of the integer division. + /// # Specification: https://www.evm.codes/#04?fork=shanghai + fn exec_div(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::LOW)?; + let popped = self.stack.pop_n(2)?; + + let a: u256 = *popped[0]; + let b: u256 = *popped[1]; + + let result: u256 = match TryInto::>::try_into(b) { + Option::Some(_) => { + // Won't panic because b is not zero + a / b + }, + Option::None => 0, + }; + + self.stack.push(result) + } + + /// 0x05 - SDIV + /// Signed division operation + /// a / b: integer result of the signed integer division. + /// If the denominator is 0, the result will be 0. + /// # Specification: https://www.evm.codes/#05?fork=shanghai + fn exec_sdiv(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::LOW)?; + let a: i256 = self.stack.pop_i256()?; + let b: i256 = self.stack.pop_i256()?; + + let result: u256 = if b == 0_u256.into() { + 0 + } else { + (a / b).into() + }; + self.stack.push(result) + } + + /// 0x06 - MOD + /// Modulo operation + /// a % b: integer result of the integer modulo. If the denominator is 0, the result will be 0. + /// # Specification: https://www.evm.codes/#06?fork=shanghai + fn exec_mod(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::LOW)?; + let popped = self.stack.pop_n(2)?; + + let a: u256 = *popped[0]; + let b: u256 = *popped[1]; + + let result: u256 = match TryInto::>::try_into(b) { + Option::Some(_) => { + // Won't panic because b is not zero + a % b + }, + Option::None => 0, + }; + + self.stack.push(result) + } + + /// 0x07 - SMOD + /// Signed modulo operation + /// a % b: integer result of the signed integer modulo. If the denominator is 0, the result will + /// be 0. + /// All values are treated as two’s complement signed 256-bit integers. Note the overflow + /// semantic when βˆ’2^255 is negated. + /// # Specification: https://www.evm.codes/#07?fork=shanghai + fn exec_smod(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::LOW)?; + let a: i256 = self.stack.pop_i256()?; + let b: i256 = self.stack.pop_i256()?; + + let result: u256 = if b == 0_u256.into() { + 0 + } else { + (a % b).into() + }; + self.stack.push(result) + } + + /// 0x08 - ADDMOD + /// Addition and modulo operation + /// (a + b) % N: integer result of the addition followed by a modulo. If the denominator is 0, + /// the result will be 0. + /// All intermediate calculations of this operation are not subject to the 2256 modulo. + /// # Specification: https://www.evm.codes/#08?fork=shanghai + fn exec_addmod(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::MID)?; + let popped = self.stack.pop_n(3)?; + + let a: u256 = *popped[0]; + let b: u256 = *popped[1]; + let n = *popped[2]; + + let result: u256 = match TryInto::>::try_into(n) { + Option::Some(nonzero_n) => { + let sum = u256_wide_add(a, b); + let (_, r) = u512_safe_div_rem_by_u256(sum, nonzero_n); + r + }, + Option::None => 0, + }; + + self.stack.push(result) + } + + /// 0x09 - MULMOD operation. + /// (a * b) % N: integer result of the multiplication followed by a modulo. + /// All intermediate calculations of this operation are not subject to the 2^256 modulo. + /// If the denominator is 0, the result will be 0. + /// # Specification: https://www.evm.codes/#09?fork=shanghai + fn exec_mulmod(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::MID)?; + let a: u256 = self.stack.pop()?; + let b: u256 = self.stack.pop()?; + let n = self.stack.pop()?; + + let result: u256 = match TryInto::>::try_into(n) { + Option::Some(n_nz) => { u256_mul_mod_n(a, b, n_nz) }, + Option::None => 0, + }; + + self.stack.push(result) + } + + /// 0x0A - EXP + /// Exponential operation + /// a ** b: integer result of raising a to the bth power modulo 2^256. + /// # Specification: https://www.evm.codes/#0a?fork=shanghai + fn exec_exp(ref self: VM) -> Result<(), EVMError> { + let base = self.stack.pop()?; + let exponent = self.stack.pop()?; + + // Gas + let bytes_used = exponent.bytes_used(); + let total_cost = gas::EXP + .checked_add(gas::EXP_GAS_PER_BYTE * bytes_used.into()) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + let result = base.wrapping_pow(exponent); + + self.stack.push(result) + } + + /// 0x0B - SIGNEXTEND + /// SIGNEXTEND takes two inputs `b` and `x` where x: integer value to sign extend + /// and b: size in byte - 1 of the integer to sign extend and extends the length of + /// x as a two’s complement signed integer. + /// The first `i` bits of the output (numbered from the /!\LEFT/!\ counting from zero) + /// are equal to the `t`-th bit of `x`, where `t` is equal to + /// `256 - 8(b + 1)`. The remaining bits of the output are equal to the corresponding bits of + /// `x`. + /// If b >= 32, then the output is x because t<=0. + /// To efficiently implement this algorithm we can implement it using a mask, which is all + /// zeroes until the t-th bit included, and all ones afterwards. The index of `t` when numbered + /// from the RIGHT is s = `255 - t` = `8b + 7`; so the integer value of the mask used is 2^s - + /// 1. + /// Let v be the t-th bit of x. If v == 1, then the output should be all 1s until the t-th bit + /// included, followed by the remaining bits of x; which is corresponds to (x | !mask). + /// If v == 0, then the output should be all 0s until the t-th bit included, followed by the + /// remaining bits of x; + /// which corresponds to (x & mask). + /// # Specification: https://www.evm.codes/#0b?fork=shanghai + /// Complex opcode, check: https://ethereum.github.io/yellowpaper/paper.pdf + fn exec_signextend(ref self: VM) -> Result<(), EVMError> { + self.charge_gas(gas::LOW)?; + let b = self.stack.pop()?; + let x = self.stack.pop()?; + + let result = if b < 32 { + let s = 8 * b + 7; + //TODO: use POW_2 table for optimization + let two_pow_s = 2.pow(s); + // Get v, the t-th bit of x. To do this we bitshift x by s bits to the right and apply a + // mask to get the last bit. + let v = (x / two_pow_s) & 1; + // Compute the mask with 8b+7 bits set to one + let mask = two_pow_s - 1; + if v == 0 { + x & mask + } else { + x | ~mask + } + } else { + x + }; + + self.stack.push(result) + } +} + +#[cfg(test)] +mod tests { + use core::num::traits::Bounded; + use core::result::ResultTrait; + use crate::instructions::StopAndArithmeticOperationsTrait; + use crate::model::vm::VMTrait; + use crate::stack::StackTrait; + use crate::test_utils::VMBuilderTrait; + + + #[test] + fn test_exec_stop_should_stop_and_empty_return_data() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.return_data = [1, 2, 3].span(); + // When + vm.exec_stop(); + + // Then + assert!(!vm.is_running()); + assert_eq!(vm.return_data, [].span()); + } + + #[test] + fn test_exec_add() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(1).expect('push failed'); + vm.stack.push(2).expect('push failed'); + vm.stack.push(3).expect('push failed'); + + // When + vm.exec_add().expect('exec_add failed'); + + // Then + assert(vm.stack.len() == 2, 'stack should have two elems'); + assert(vm.stack.peek().unwrap() == 5, 'stack top should be 3+2'); + assert(vm.stack.peek_at(1).unwrap() == 1, 'stack[1] should be 1'); + } + + #[test] + fn test_exec_add_overflow() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(Bounded::::MAX).unwrap(); + vm.stack.push(1).expect('push failed'); + + // When + vm.exec_add().expect('exec_add failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + #[test] + fn test_exec_mul() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(4).expect('push failed'); + vm.stack.push(5).expect('push failed'); + + // When + vm.exec_mul().expect('exec_mul failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 20, 'stack top should be 4*5'); + } + + #[test] + fn test_exec_mul_overflow() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(Bounded::::MAX).unwrap(); + vm.stack.push(2).expect('push failed'); + + // When + vm.exec_mul().expect('exec_mul failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == Bounded::::MAX - 1, 'expected MAX_U256 -1'); + } + + #[test] + fn test_exec_sub() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(7).expect('push failed'); + vm.stack.push(10).expect('push failed'); + + // When + vm.exec_sub().expect('exec_sub failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 3, 'stack top should be 10-7'); + } + + #[test] + fn test_exec_sub_underflow() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(1).expect('push failed'); + vm.stack.push(0).expect('push failed'); + + // When + vm.exec_sub().expect('exec_sub failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == Bounded::::MAX, 'stack top should be MAX_U256'); + } + + + #[test] + fn test_exec_div() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(4).expect('push failed'); + vm.stack.push(100).expect('push failed'); + + // When + vm.exec_div().expect('exec_div failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 25, 'stack top should be 100/4'); + } + + #[test] + fn test_exec_div_by_zero() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(100).expect('push failed'); + + // When + vm.exec_div().expect('exec_div failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + #[test] + fn test_exec_sdiv_pos() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(5).expect('push failed'); + vm.stack.push(10).expect('push failed'); + + // When + vm.exec_sdiv().expect('exec_sdiv failed'); // 10 / 5 + + // Then + assert(vm.stack.len() == 1, 'stack len should be 1'); + assert(vm.stack.peek().unwrap() == 2, 'ctx not stopped'); + } + + #[test] + fn test_exec_sdiv_neg() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(Bounded::MAX).unwrap(); + vm.stack.push(2).expect('push failed'); + + // When + vm.exec_sdiv().expect('exec_sdiv failed'); // 2 / -1 + + // Then + assert(vm.stack.len() == 1, 'stack len should be 1'); + assert(vm.stack.peek().unwrap() == Bounded::MAX - 1, 'sdiv_neg failed'); + } + + #[test] + fn test_exec_sdiv_by_0() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(10).expect('push failed'); + + // When + vm.exec_sdiv().expect('exec_sdiv failed'); + + // Then + assert(vm.stack.len() == 1, 'stack len should be 1'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + #[test] + fn test_exec_mod() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(6).expect('push failed'); + vm.stack.push(100).expect('push failed'); + + // When + vm.exec_mod().expect('exec_mod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 4, 'stack top should be 100%6'); + } + + #[test] + fn test_exec_mod_by_zero() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(100).expect('push failed'); + + // When + vm.exec_smod().expect('exec_smod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 100%6'); + } + + #[test] + fn test_exec_smod() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(3).expect('push failed'); + vm.stack.push(10).expect('push failed'); + + // When + vm.exec_smod().expect('exec_smod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 1, 'stack top should be 10%3 = 1'); + } + + #[test] + fn test_exec_smod_neg() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD) + .unwrap(); // -3 + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8) + .unwrap(); // -8 + + // When + vm.exec_smod().expect('exec_smod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm + .stack + .peek() + .unwrap() == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, + 'stack top should be -8%-3 = -2' + ); + } + + #[test] + fn test_exec_smod_zero() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(10).expect('push failed'); + + // When + vm.exec_mod().expect('exec_mod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + + #[test] + fn test_exec_addmod() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(7).expect('push failed'); + vm.stack.push(10).expect('push failed'); + vm.stack.push(20).expect('push failed'); + + // When + vm.exec_addmod().expect('exec_addmod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 2, 'stack top should be (10+20)%7'); + } + + #[test] + fn test_exec_addmod_by_zero() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(10).expect('push failed'); + vm.stack.push(20).expect('push failed'); + + // When + vm.exec_addmod().expect('exec_addmod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); + } + + + #[test] + fn test_exec_addmod_overflow() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(3).expect('push failed'); + vm.stack.push(2).expect('push failed'); + vm.stack.push(Bounded::::MAX).unwrap(); + + // When + vm.exec_addmod().expect('exec_addmod failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm.stack.peek().unwrap() == 2, 'stack top should be 2' + ); // (MAX_U256 + 2) % 3 = (2^256 + 1) % 3 = 2 + } + + #[test] + fn test_mulmod_basic() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(10).expect('push failed'); + vm.stack.push(7).expect('push failed'); + vm.stack.push(5).expect('push failed'); + + // When + vm.exec_mulmod().expect('exec_mulmod failed'); + + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 5, 'stack top should be 5'); // (5 * 7) % 10 = 5 + } + + #[test] + fn test_mulmod_zero_modulus() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(7).expect('push failed'); + vm.stack.push(5).expect('push failed'); + + vm.exec_mulmod().expect('exec_mulmod failed'); + + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); // modulus is 0 + } + + #[test] + fn test_mulmod_overflow() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(12).expect('push failed'); + vm.stack.push(Bounded::::MAX).unwrap(); + vm.stack.push(Bounded::::MAX).unwrap(); + + vm.exec_mulmod().expect('exec_mulmod failed'); + + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm.stack.peek().unwrap() == 9, 'stack top should be 1' + ); // (MAX_U256 * MAX_U256) % 12 = 9 + } + + #[test] + fn test_mulmod_zero() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(10).expect('push failed'); + vm.stack.push(7).expect('push failed'); + vm.stack.push(0).expect('push failed'); + + vm.exec_mulmod().expect('exec_mulmod failed'); + + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 0, 'stack top should be 0'); // 0 * 7 % 10 = 0 + } + + #[test] + fn test_exec_exp() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let initial_gas = vm.gas_left(); + vm.stack.push(2).expect('push failed'); + vm.stack.push(10).expect('push failed'); + + // When + vm.exec_exp().expect('exec exp failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.peek().unwrap() == 100, 'stack top should be 100'); + let expected_gas_used = 10 + 50 * 1; + assert_eq!(initial_gas - vm.gas_left(), expected_gas_used); + } + + #[test] + fn test_exec_exp_overflow() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let initial_gas = vm.gas_left(); + vm.stack.push(2).expect('push failed'); + vm.stack.push(Bounded::::MAX.into() + 1).unwrap(); + + // When + vm.exec_exp().expect('exec exp failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm.stack.peek().unwrap() == 0, 'stack top should be 0' + ); // (2^128)^2 = 2^256 = 0 % 2^256 + let expected_gas_used = 10 + 50 * 1; + assert_eq!(initial_gas - vm.gas_left(), expected_gas_used); + } + + #[test] + fn test_exec_signextend() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0xFF).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + vm.exec_signextend().expect('exec_signextend failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm + .stack + .peek() + .unwrap() == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 'stack top should be MAX_u256 -1' + ); + } + + #[test] + fn test_exec_signextend_no_effect() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm.stack.push(0x7F).expect('push failed'); + vm.stack.push(0x00).expect('push failed'); + + // When + vm.exec_signextend().expect('exec_signextend failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm.stack.peek().unwrap() == 0x7F, 'stack top should be 0x7F' + ); // The 248-th bit of x is 0, so the output is not changed. + } + + #[test] + fn test_exec_signextend_on_negative() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + vm + .stack + .push(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0001) + .expect('push failed'); + vm.stack.push(0x01).expect('push failed'); // s = 15, v = 0 + + // When + vm.exec_signextend().expect('exec_signextend failed'); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert( + vm.stack.peek().unwrap() == 0x01, 'stack top should be 0' + ); // The 241-th bit of x is 0, so all bits before t are switched to 0 + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/instructions/system_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/instructions/system_operations.cairo new file mode 100644 index 000000000..65b95d75d --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/instructions/system_operations.cairo @@ -0,0 +1,1188 @@ +use core::num::traits::CheckedAdd; +use crate::call_helpers::CallHelpers; +use crate::create_helpers::{CreateHelpers, CreateType}; +use crate::errors::{ensure, EVMError}; +use crate::gas; +use crate::memory::MemoryTrait; +use crate::model::Transfer; +use crate::model::account::{AccountTrait}; +use crate::model::vm::{VM, VMTrait}; +use crate::stack::StackTrait; +use crate::state::StateTrait; +use utils::set::SetTrait; + +#[generate_trait] +pub impl SystemOperations of SystemOperationsTrait { + /// CREATE + /// # Specification: https://www.evm.codes/#f0?fork=shanghai + fn exec_create(ref self: VM) -> Result<(), EVMError> { + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + + let create_args = self.prepare_create(CreateType::Create)?; + self.generic_create(create_args) + } + + + /// CALL + /// # Specification: https://www.evm.codes/#f1?fork=shanghai + fn exec_call(ref self: VM) -> Result<(), EVMError> { + let gas = self.stack.pop_saturating_u64()?; + let to = self.stack.pop_eth_address()?; + let value = self.stack.pop()?; + let args_offset = self.stack.pop_saturating_usize()?; + let args_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + let ret_offset = self.stack.pop_saturating_usize()?; + let ret_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + // GAS + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(args_offset, args_size), (ret_offset, ret_size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + + let access_gas_cost = if self.accessed_addresses.contains(to) { + gas::WARM_ACCESS_COST + } else { + self.accessed_addresses.add(to); + gas::COLD_ACCOUNT_ACCESS_COST + }; + + let create_gas_cost = if self.env.state.is_account_alive(to) || value == 0 { + 0 + } else { + gas::NEWACCOUNT + }; + + let transfer_gas_cost = if value != 0 { + gas::CALLVALUE + } else { + 0 + }; + + let message_call_gas = gas::calculate_message_call_gas( + value, + gas, + self.gas_left(), + memory_expansion.expansion_cost, + access_gas_cost + transfer_gas_cost + create_gas_cost + )?; + let total_cost = message_call_gas + .cost + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + // Only the transfer gas is left to charge. + + let read_only = self.message().read_only; + + // Check if current context is read only that value == 0. + // De Morgan's law: !(read_only && value != 0) == !read_only || value == 0 + ensure(!read_only || value == 0, EVMError::WriteInStaticContext)?; + + // If sender_balance < value, return early, pushing + // 0 on the stack to indicate call failure. + // The gas cost relative to the transfer is refunded. + let sender_balance = self.env.state.get_account(self.message().target.evm).balance(); + if sender_balance < value { + self.return_data = [].span(); + self.gas_left += message_call_gas.stipend; + return self.stack.push(0); + } + + self + .generic_call( + gas: message_call_gas.stipend, + :value, + caller: self.message().target.evm, + :to, + code_address: to, + should_transfer_value: true, + is_staticcall: false, + :args_offset, + :args_size, + :ret_offset, + :ret_size, + ) + } + + + /// CALLCODE + /// # Specification: https://www.evm.codes/#f2?fork=shanghai + fn exec_callcode(ref self: VM) -> Result<(), EVMError> { + let gas = self.stack.pop_saturating_u64()?; + let code_address = self.stack.pop_eth_address()?; + let value = self.stack.pop()?; + let args_offset = self.stack.pop_saturating_usize()?; + let args_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + let ret_offset = self.stack.pop_saturating_usize()?; + let ret_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + let to = self.message().target.evm; + + // GAS + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(args_offset, args_size), (ret_offset, ret_size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + + let access_gas_cost = if self.accessed_addresses.contains(code_address) { + gas::WARM_ACCESS_COST + } else { + self.accessed_addresses.add(code_address); + gas::COLD_ACCOUNT_ACCESS_COST + }; + + let transfer_gas_cost = if value != 0 { + gas::CALLVALUE + } else { + 0 + }; + + let message_call_gas = gas::calculate_message_call_gas( + value, + gas, + self.gas_left(), + memory_expansion.expansion_cost, + access_gas_cost + transfer_gas_cost + )?; + self.charge_gas(message_call_gas.cost + memory_expansion.expansion_cost)?; + + // If sender_balance < value, return early, pushing + // 0 on the stack to indicate call failure. + // The gas cost relative to the transfer is refunded. + let sender_balance = self.env.state.get_account(self.message().target.evm).balance(); + if sender_balance < value { + self.return_data = [].span(); + self.gas_left += message_call_gas.stipend; + return self.stack.push(0); + } + + self + .generic_call( + message_call_gas.stipend, + value, + self.message().target.evm, + to, + code_address, + true, + false, + args_offset, + args_size, + ret_offset, + ret_size, + ) + } + /// RETURN + /// # Specification: https://www.evm.codes/#f3?fork=shanghai + fn exec_return(ref self: VM) -> Result<(), EVMError> { + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + self.charge_gas(gas::ZERO + memory_expansion.expansion_cost)?; + + let mut return_data = Default::default(); + self.memory.load_n(size, ref return_data, offset); + + // Set the memory data to the parent context return data + // and halt the context. + self.set_return_data(return_data.span()); + self.stop(); + Result::Ok(()) + } + + /// DELEGATECALL + /// # Specification: https://www.evm.codes/#f4?fork=shanghai + fn exec_delegatecall(ref self: VM) -> Result<(), EVMError> { + let gas = self.stack.pop_saturating_u64()?; + let code_address = self.stack.pop_eth_address()?; + let args_offset = self.stack.pop_saturating_usize()?; + let args_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + let ret_offset = self.stack.pop_saturating_usize()?; + let ret_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + // GAS + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(args_offset, args_size), (ret_offset, ret_size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + + let access_gas_cost = if self.accessed_addresses.contains(code_address) { + gas::WARM_ACCESS_COST + } else { + self.accessed_addresses.add(code_address); + gas::COLD_ACCOUNT_ACCESS_COST + }; + + let message_call_gas = gas::calculate_message_call_gas( + 0, gas, self.gas_left(), memory_expansion.expansion_cost, access_gas_cost + )?; + let total_cost = message_call_gas + .cost + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(total_cost)?; + + self + .generic_call( + message_call_gas.stipend, + self.message().value, + self.message().caller.evm, + self.message().target.evm, + code_address, + false, + false, + args_offset, + args_size, + ret_offset, + ret_size, + ) + } + + /// CREATE2 + /// # Specification: https://www.evm.codes/#f5?fork=shanghai + fn exec_create2(ref self: VM) -> Result<(), EVMError> { + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + + let create_args = self.prepare_create(CreateType::Create2)?; + self.generic_create(create_args) + } + + /// STATICCALL + /// # Specification: https://www.evm.codes/#fa?fork=shanghai + fn exec_staticcall(ref self: VM) -> Result<(), EVMError> { + let gas = self.stack.pop_saturating_u64()?; + let to = self.stack.pop_eth_address()?; + let args_offset = self.stack.pop_saturating_usize()?; + let args_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + let ret_offset = self.stack.pop_saturating_usize()?; + let ret_size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + // GAS + let memory_expansion = gas::memory_expansion( + self.memory.size(), [(args_offset, args_size), (ret_offset, ret_size)].span() + )?; + self.memory.ensure_length(memory_expansion.new_size); + + let access_gas_cost = if self.accessed_addresses.contains(to) { + gas::WARM_ACCESS_COST + } else { + self.accessed_addresses.add(to); + gas::COLD_ACCOUNT_ACCESS_COST + }; + + let message_call_gas = gas::calculate_message_call_gas( + 0, gas, self.gas_left(), memory_expansion.expansion_cost, access_gas_cost + )?; + let gas_to_charge = message_call_gas + .cost + .checked_add(memory_expansion.expansion_cost) + .ok_or(EVMError::OutOfGas)?; + self.charge_gas(gas_to_charge)?; + + self + .generic_call( + message_call_gas.stipend, + 0, + self.message().target.evm, + to, + to, + true, + true, + args_offset, + args_size, + ret_offset, + ret_size, + ) + } + + + /// REVERT + /// # Specification: https://www.evm.codes/#fd?fork=shanghai + fn exec_revert(ref self: VM) -> Result<(), EVMError> { + let offset = self.stack.pop_saturating_usize()?; + let size = self.stack.pop_usize()?; // Any size bigger than a usize would MemoryOOG. + + let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; + self.memory.ensure_length(memory_expansion.new_size); + self.charge_gas(memory_expansion.expansion_cost)?; + + let mut return_data = Default::default(); + self.memory.load_n(size, ref return_data, offset); + + // Set the memory data to the parent context return data + // and halt the context. + self.set_return_data(return_data.span()); + self.stop(); + self.set_error(); + Result::Ok(()) + } + + /// INVALID + /// # Specification: https://www.evm.codes/#fe?fork=shanghai + fn exec_invalid(ref self: VM) -> Result<(), EVMError> { + Result::Err(EVMError::InvalidOpcode(0xfe)) + } + + + /// SELFDESTRUCT + /// # Specification: https://www.evm.codes/#ff?fork=shanghai + fn exec_selfdestruct(ref self: VM) -> Result<(), EVMError> { + let recipient = self.stack.pop_eth_address()?; + + // GAS + let mut gas_cost = gas::SELFDESTRUCT; + if !self.accessed_addresses.contains(recipient) { + self.accessed_addresses.add(recipient); + gas_cost += gas::COLD_ACCOUNT_ACCESS_COST; + }; + + let mut self_account = self.env.state.get_account(self.message().target.evm); + let self_balance = self_account.balance(); + if (!self.env.state.is_account_alive(recipient) && self_balance != 0) { + gas_cost += gas::NEWACCOUNT; + } + self.charge_gas(gas_cost)?; + + // Operation + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + + // If the account was created in the same transaction and recipient is self, the native + // token is burnt + let recipient_evm_address = if (self_account.is_created + && recipient == self_account.evm_address()) { + 0.try_into().unwrap() + } else { + recipient + }; + let recipient_account = self.env.state.get_account(recipient_evm_address); + // Transfer balance + self + .env + .state + .add_transfer( + Transfer { + sender: self_account.address(), + recipient: recipient_account.address(), + amount: self_balance + } + )?; + + //@dev: get_account again because add_transfer modified its balance + self_account = self.env.state.get_account(self.message().target.evm); + // Register for selfdestruct + self_account.selfdestruct(); + self.env.state.set_account(self_account); + self.stop(); + Result::Ok(()) + } +} + +#[cfg(test)] +mod tests { + use contracts::test_data::{storage_evm_bytecode, storage_evm_initcode}; + use core::result::ResultTrait; + use core::starknet::EthAddress; + use core::traits::TryInto; + use crate::call_helpers::CallHelpersImpl; + use crate::instructions::MemoryOperationTrait; + use crate::instructions::SystemOperationsTrait; + use crate::interpreter::{EVMTrait}; + use crate::model::account::{Account}; + use crate::model::vm::VMTrait; + use crate::model::{AccountTrait, Address}; + use crate::stack::StackTrait; + use crate::state::{StateTrait}; + use crate::test_utils::{ + VMBuilderTrait, MemoryTestUtilsTrait, native_token, evm_address, setup_test_environment, + origin, uninitialized_account + }; + use snforge_std::{test_address, start_mock_call}; + use utils::constants::EMPTY_KECCAK; + use utils::helpers::compute_starknet_address; + use utils::traits::bytes::{U8SpanExTrait, FromBytes}; + + use utils::traits::{EthAddressIntoU256}; + + + #[test] + fn test_exec_return() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + // When + vm.stack.push(1000).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.exec_mstore().expect('exec_mstore failed'); + + vm.stack.push(32).expect('push failed'); + vm.stack.push(0).expect('push failed'); + assert(vm.exec_return().is_ok(), 'Exec return failed'); + + let return_data = vm.return_data(); + let parsed_return_data: u256 = return_data + .from_be_bytes() + .expect('Failed to parse return data'); + assert(1000 == parsed_return_data, 'Wrong return_data'); + assert(!vm.is_running(), 'vm should be stopped'); + assert_eq!(vm.error, false); + } + + #[test] + fn test_exec_revert() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + // When + vm.stack.push(1000).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.exec_mstore().expect('exec_mstore failed'); + + vm.stack.push(32).expect('push failed'); + vm.stack.push(0).expect('push failed'); + assert(vm.exec_revert().is_ok(), 'Exec revert failed'); + + let return_data = vm.return_data(); + let parsed_return_data: u256 = return_data + .from_be_bytes() + .expect('Failed to parse return data'); + assert(1000 == parsed_return_data, 'Wrong return_data'); + assert(!vm.is_running(), 'vm should be stopped'); + assert_eq!(vm.error, true); + } + + #[test] + fn test_exec_return_with_offset() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + // When + vm.stack.push(1).expect('push failed'); + vm.stack.push(0).expect('push failed'); + vm.exec_mstore().expect('exec_mstore failed'); + + vm.stack.push(32).expect('push failed'); + vm.stack.push(1).expect('push failed'); + assert(vm.exec_return().is_ok(), 'Exec return failed'); + let return_data = vm.return_data(); + let parsed_return_data: u256 = return_data + .from_be_bytes_partial() + .expect('Failed to parse return data'); + assert(256 == parsed_return_data, 'Wrong return_data'); + assert(!vm.is_running(), 'vm should be stopped'); + assert_eq!(vm.error, false); + } + + #[test] + fn test_exec_call() { + // Given + + // Set vm bytecode + // (call 0xffffff 0xabfa740ccd 0 0 0 0 1) + let bytecode = [ + 0x60, + 0x01, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x64, + 0xab, + 0xfa, + 0x74, + 0x0c, + 0xcd, + 0x62, + 0xff, + 0xff, + 0xff, + // CALL + 0xf1, + 0x00 + ].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let caller_account = Account { + address: vm.message().target, + balance: 0, + code: bytecode, + code_hash: code_hash, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(caller_account); + + // Deploy bytecode at 0xabfa740ccd + // SSTORE 0x42 at 0x42 + let eth_address: EthAddress = 0xabfa740ccd_u256.into(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let deployed_bytecode = [ + 0x60, + 0x01, + 0x60, + 0x01, + 0x01, + 0x60, + 0x00, + 0x53, + 0x60, + 0x42, + 0x60, + 0x42, + 0x55, + 0x60, + 0x20, + 0x60, + 0x00, + 0xf3 + ].span(); + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); + + // When + EVMTrait::execute_code(ref vm); + + // Then + assert!(!vm.is_error()); + assert!(!vm.is_running()); + let storage_val = vm.env.state.read_state(contract_account.address.evm, 0x42); + assert_eq!(storage_val, 0x42); + } + + #[test] + fn test_should_fail_exec_staticcall() { + // Given + + // Set vm bytecode + // (staticcall 0xffffff 0xabfa740ccd 0 0 0 0 1) + let bytecode = [ + 0x60, + 0x01, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x64, + 0xab, + 0xfa, + 0x74, + 0x0c, + 0xcd, + 0x62, + 0xff, + 0xff, + 0xff, + // STATICCALL + 0xfa, + 0x00 + ].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let caller_account = Account { + address: vm.message().target, + balance: 0, + code: bytecode, + code_hash: code_hash, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(caller_account); + + // Deploy bytecode at 0xabfa740ccd + // SSTORE 0x42 at 0x42 + let eth_address: EthAddress = 0xabfa740ccd_u256.into(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let deployed_bytecode = [ + 0x60, + 0x01, + 0x60, + 0x01, + 0x01, + 0x60, + 0x00, + 0x53, + 0x60, + 0x42, + 0x60, + 0x42, + 0x55, + 0x60, + 0x20, + 0x60, + 0x00, + 0xf3 + ].span(); + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); + + // When + EVMTrait::execute_code(ref vm); + + // Then + assert!(!vm.is_error()); + assert!(!vm.is_running()); + assert_eq!(vm.stack.peek().unwrap(), 0); // STATICCALL should fail because of SSTORE + } + + + #[test] + fn test_exec_call_code() { + // Given + // Set vm bytecode + // (callcode 0xffffff 0x1234 0 0 0 0 1) + let bytecode = [ + 0x60, + 0x01, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x61, + 0x12, + 0x34, + 0x62, + 0xff, + 0xff, + 0xff, + // CALLCODE + 0xf2, + 0x00 + ].span(); + let _code_hash = bytecode.compute_keccak256_hash(); + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let eoa_account = Account { + address: vm.message().target, + balance: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(eoa_account); + + // Deploy bytecode at 0x1234 + // SSTORE 0x42 at 0x42 + let deployed_bytecode = [ + 0x60, + 0x01, + 0x60, + 0x01, + 0x01, + 0x60, + 0x00, + 0x53, + 0x60, + 0x42, + 0x60, + 0x42, + 0x55, + 0x60, + 0x20, + 0x60, + 0x00, + 0xf3 + ].span(); + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let eth_address: EthAddress = 0x1234.try_into().unwrap(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); + + // When + EVMTrait::execute_code(ref vm); + + // Then + assert!(!vm.is_error()); + assert!(!vm.is_running()); + + let storage_val = vm.env.state.read_state(vm.message.target.evm, 0x42); + + assert_eq!(storage_val, 0x42); + } + + #[test] + fn test_exec_delegatecall() { + // Given + + // Set vm bytecode + // (delegatecall 0xffffff 0x1234 0 0 0 0 1) + let bytecode = [ + 0x60, + 0x01, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x61, + 0x12, + 0x34, + 0x62, + 0xff, + 0xff, + 0xff, + // DELEGATECALL + 0xf4, + 0x00 + ].span(); + let _code_hash = bytecode.compute_keccak256_hash(); + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + let eoa_account = Account { + address: vm.message().target, + balance: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(eoa_account); + + // SSTORE 0x42 at 0x42 + let deployed_bytecode = [ + 0x60, + 0x01, + 0x60, + 0x01, + 0x01, + 0x60, + 0x00, + 0x53, + 0x60, + 0x42, + 0x60, + 0x42, + 0x55, + 0x60, + 0x20, + 0x60, + 0x00, + 0xf3 + ].span(); + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let eth_address: EthAddress = 0x1234.try_into().unwrap(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 0, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + vm.env.state.set_account(contract_account); + + // When + EVMTrait::execute_code(ref vm); + + // Then + assert!(!vm.is_error()); + assert!(!vm.is_running()); + + let storage_val = vm.env.state.read_state(vm.message.target.evm, 0x42); + + assert_eq!(storage_val, 0x42); + } + + //! In the exec_create tests, we query the balance of the contract being created by doing a + //! starknet_call to the native token. + //! Thus, we must store the native token address in the Kakarot storage preemptively. + //! As such, the address computation uses the uninitialized account class. + #[test] + fn test_exec_create_no_value_transfer() { + // Given + setup_test_environment(); + + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, uninitialized_account() + ); + let origin_account = Account { + address: Address { + evm: origin(), + starknet: compute_starknet_address( + test_address(), origin(), uninitialized_account() + ) + }, + balance: 2, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .build(); + vm.env.state.set_account(contract_account); + vm.env.state.set_account(origin_account); + + // Load into memory the bytecode of Storage.sol + let storage_initcode = storage_evm_initcode(); + vm.memory.store_n_with_expansion(storage_initcode, 0); + + vm.stack.push(storage_initcode.len().into()).unwrap(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_create().unwrap(); + EVMTrait::execute_code(ref vm); + + // computed using `compute_create_address` script + // run `bun run compute_create_address` -> CREATE -> EthAddress = evm_address() -> + //nonce = 1 + let account = vm + .env + .state + .get_account(0x930b3d8D35621F2e27Db700cA5D16Df771642fdD.try_into().unwrap()); + + assert_eq!(account.nonce(), 1); + assert_eq!(account.code, storage_evm_bytecode()); + assert_eq!(account.balance(), 0); + + let deployer = vm.env.state.get_account(eth_address); + assert_eq!(deployer.nonce(), 2); + assert_eq!(deployer.balance(), 2); + } + //TODO add test with value transfer + + #[test] + fn test_exec_create_failure() { + // Given + setup_test_environment(); + + let deployed_bytecode = [0xFF].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let origin_account = Account { + address: Address { + evm: origin(), + starknet: compute_starknet_address( + test_address(), origin(), uninitialized_account() + ), + }, + balance: 2, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let deployer = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets().with_target(deployer.address).build(); + vm.env.state.set_account(deployer); + vm.env.state.set_account(origin_account); + + // Load into memory the bytecode to init, which is the revert opcode + let revert_initcode = [0xFD].span(); + vm.memory.store_n_with_expansion(revert_initcode, 0); + + vm.stack.push(revert_initcode.len().into()).unwrap(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(1).expect('push failed'); + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_create().expect('exec_create failed'); + EVMTrait::execute_code(ref vm); + + let expected_address = 0x930b3d8D35621F2e27Db700cA5D16Df771642fdD.try_into().unwrap(); + + // computed using `compute_create_address` script + let account = vm.env.state.get_account(expected_address); + assert_eq!(account.nonce(), 0); + assert_eq!(account.code.len(), 0); + assert_eq!(account.balance(), 0); + + let deployer = vm.env.state.get_account(eth_address); + assert_eq!(deployer.nonce(), 2); + assert_eq!(deployer.balance(), 2); + } + + #[test] + fn test_exec_create2() { + // Given + setup_test_environment(); + + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let origin_account = Account { + address: Address { + evm: origin(), + starknet: compute_starknet_address( + test_address(), origin(), uninitialized_account() + ), + }, + balance: 2, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets() + .with_caller(contract_account.address) + .build(); + + vm.env.state.set_account(origin_account); + vm.env.state.set_account(contract_account); + + // Load into memory the bytecode of Storage.sol + let storage_initcode = storage_evm_initcode(); + vm.memory.store_n_with_expansion(storage_initcode, 0); + + vm.stack.push(0).expect('push failed'); + vm.stack.push(storage_initcode.len().into()).unwrap(); + vm.stack.push(0).expect('push failed'); + vm.stack.push(0).expect('push failed'); + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_create2().unwrap(); + EVMTrait::execute_code(ref vm); + + assert!(!vm.is_running() && !vm.is_error()); + + // Add SNJS script to precompute the address of the Storage.sol contract + // import { getContractAddress } from 'viem' + + // const address = getContractAddress({ + // bytecode: + // + // '0x608060405234801561000f575f80fd5b506101438061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80632e64cec1146100385780636057361d14610056575b5f80fd5b610040610072565b60405161004d919061009b565b60405180910390f35b610070600480360381019061006b91906100e2565b61007a565b005b5f8054905090565b805f8190555050565b5f819050919050565b61009581610083565b82525050565b5f6020820190506100ae5f83018461008c565b92915050565b5f80fd5b6100c181610083565b81146100cb575f80fd5b50565b5f813590506100dc816100b8565b92915050565b5f602082840312156100f7576100f66100b4565b5b5f610104848285016100ce565b9150509291505056fea2646970667358221220b5c3075f2f2034d039a227fac6dd314b052ffb2b3da52c7b6f5bc374d528ed3664736f6c63430008140033', + // from: '0x00000000000000000065766d5f61646472657373', opcode: 'CREATE2', + //salt: '0x00', + // }); + // console.log(address) + let account = vm + .env + .state + .get_account(0x0f48B8c382B5234b1a92368ee0f6864a429d0Cb8.try_into().unwrap()); + + assert(account.nonce() == 1, 'wrong nonce'); + assert(account.code == storage_evm_bytecode(), 'wrong bytecode'); + } + + #[test] + fn test_exec_selfdestruct_should_fail_if_readonly() { + // Given + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .with_read_only() + .build(); + vm.env.state.set_account(contract_account); + + // When + vm.stack.push(contract_account.address.evm.into()).unwrap(); + let res = vm.exec_selfdestruct(); + // Then + assert!(res.is_err()) + } + + #[test] + fn test_exec_selfdestruct_should_burn_tokens_if_created_same_tx_and_recipient_self() { + // Given + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: true, + selfdestruct: false, + }; + let burn_account = Account { + address: Address { + evm: 0.try_into().unwrap(), + starknet: compute_starknet_address( + test_address(), 0.try_into().unwrap(), 0.try_into().unwrap() + ), + }, + balance: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .build(); + vm.env.state.set_account(burn_account); + vm.env.state.set_account(contract_account); + + // When + vm.stack.push(contract_account.address.evm.into()).unwrap(); + vm.exec_selfdestruct().expect('selfdestruct failed'); + + // Then + let contract_account = vm.env.state.get_account(contract_account.address.evm); + assert!(contract_account.is_selfdestruct()); + assert_eq!(contract_account.balance(), 0); + + let burn_account = vm.env.state.get_account(burn_account.address.evm); + assert_eq!(burn_account.balance(), 2); + } + + #[test] + fn test_exec_selfdestruct_should_transfer_balance_to_recipient() { + // Given + let deployed_bytecode = [0xff].span(); + let eth_address: EthAddress = evm_address(); + let starknet_address = compute_starknet_address( + test_address(), eth_address, 0.try_into().unwrap() + ); + let code_hash = deployed_bytecode.compute_keccak256_hash(); + let contract_account = Account { + address: Address { evm: eth_address, starknet: starknet_address, }, + balance: 2, + code: deployed_bytecode, + code_hash: code_hash, + nonce: 1, + is_created: false, + selfdestruct: false, + }; + let recipient = Account { + address: Address { + evm: 'recipient'.try_into().unwrap(), + starknet: compute_starknet_address( + test_address(), 'recipient'.try_into().unwrap(), 0.try_into().unwrap() + ), + }, + balance: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + is_created: false, + selfdestruct: false, + }; + + let mut vm = VMBuilderTrait::new_with_presets() + .with_target(contract_account.address) + .build(); + vm.env.state.set_account(contract_account); + vm.env.state.set_account(recipient); + + // When + vm.stack.push(recipient.address.evm.into()).unwrap(); + vm.exec_selfdestruct().expect('selfdestruct failed'); + + // Then + let contract_account = vm.env.state.get_account(contract_account.address.evm); + assert!(contract_account.is_selfdestruct()); + assert_eq!(contract_account.balance(), 0); + + let recipient = vm.env.state.get_account(recipient.address.evm); + assert_eq!(recipient.balance(), 2); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/interpreter.cairo b/cairo/kakarot-ssj/crates/evm/src/interpreter.cairo new file mode 100644 index 000000000..54fb0a52c --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/interpreter.cairo @@ -0,0 +1,1115 @@ +use contracts::kakarot_core::KakarotCore; +use contracts::kakarot_core::interface::IKakarotCore; +use core::num::traits::Zero; +use core::ops::SnapshotDeref; +use core::starknet::EthAddress; +use core::starknet::storage::{StoragePointerReadAccess}; +use crate::backend::starknet_backend; +use crate::create_helpers::CreateHelpers; +use crate::errors::{EVMError, EVMErrorTrait}; + +use crate::instructions::{ + ExchangeOperationsTrait, LoggingOperationsTrait, StopAndArithmeticOperationsTrait, + ComparisonAndBitwiseOperationsTrait, SystemOperationsTrait, BlockInformationTrait, + DuplicationOperationsTrait, EnvironmentInformationTrait, PushOperationsTrait, + MemoryOperationTrait, Sha3Trait +}; + +use crate::model::account::{Account, AccountTrait}; +use crate::model::vm::{VM, VMTrait}; +use crate::model::{ + Message, Environment, Transfer, ExecutionSummary, ExecutionResult, ExecutionResultTrait, + ExecutionResultStatus, AddressTrait, TransactionResult, Address +}; +use crate::precompiles::Precompiles; +use crate::precompiles::eth_precompile_addresses; +use crate::state::StateTrait; +use utils::address::compute_contract_address; +use utils::constants; +use utils::eth_transaction::common::TxKind; +use utils::eth_transaction::eip2930::{AccessListItem, AccessListItemTrait}; +use utils::eth_transaction::transaction::{Transaction, TransactionTrait}; +use utils::set::{Set, SetTrait}; +use utils::traits::eth_address::EthAddressExTrait; + +#[generate_trait] +pub impl EVMImpl of EVMTrait { + fn prepare_message( + self: @KakarotCore::ContractState, + tx: @Transaction, + sender_account: @Account, + ref env: Environment, + gas_left: u64 + ) -> (Message, bool) { + let (to, is_deploy_tx, code, code_address, calldata) = match tx.kind() { + TxKind::Create => { + let origin_nonce: u64 = sender_account.nonce(); + let to_evm_address = compute_contract_address( + sender_account.address().evm, origin_nonce + ); + let to_starknet_address = self.get_starknet_address(to_evm_address); + let to = Address { evm: to_evm_address, starknet: to_starknet_address }; + (to, true, tx.input(), Zero::zero(), [].span()) + }, + TxKind::Call(to) => { + let target_starknet_address = self.get_starknet_address(to); + let to = Address { evm: to, starknet: target_starknet_address }; + let code = env.state.get_account(to.evm).code; + (to, false, code, to, tx.input()) + } + }; + + let mut accessed_addresses: Set = Default::default(); + accessed_addresses.add(env.coinbase); + accessed_addresses.add(to.evm); + accessed_addresses.add(env.origin.evm); + accessed_addresses.extend(eth_precompile_addresses().spanset()); + + let mut accessed_storage_keys: Set<(EthAddress, u256)> = Default::default(); + + if let Option::Some(mut access_list) = tx.access_list() { + for access_list_item in access_list { + let AccessListItem { ethereum_address, storage_keys: _ } = *access_list_item; + let storage_keys = access_list_item.to_storage_keys(); + accessed_addresses.add(ethereum_address); + accessed_storage_keys.extend_from_span(storage_keys); + } + }; + + let message = Message { + caller: env.origin, + target: to, + gas_limit: gas_left, + data: calldata, + code, + code_address: code_address, + value: tx.value(), + should_transfer_value: true, + depth: 0, + read_only: false, + accessed_addresses: accessed_addresses.spanset(), + accessed_storage_keys: accessed_storage_keys.spanset(), + }; + + (message, is_deploy_tx) + } + + fn process_transaction( + ref self: KakarotCore::ContractState, origin: Address, tx: Transaction, intrinsic_gas: u64 + ) -> TransactionResult { + // Charge the cost of intrinsic gas - which has been verified to be <= gas_limit. + let block_base_fee = self.snapshot_deref().Kakarot_base_fee.read(); + let gas_price = tx.effective_gas_price(Option::Some(block_base_fee.into())); + let gas_left = tx.gas_limit() - intrinsic_gas; + let max_fee = tx.gas_limit().into() * gas_price; + let mut env = starknet_backend::get_env(origin, gas_price); + + let (message, is_deploy_tx) = { + let mut sender_account = env.state.get_account(origin.evm); + + // Charge the intrinsic gas to the sender so that it's not available for the execution + // of the transaction but don't trigger any actual transfer, as only the actual consumed + // gas is charged at the end of the transaction + sender_account.set_balance(sender_account.balance() - max_fee.into()); + + let (message, is_deploy_tx) = self + .prepare_message(@tx, @sender_account, ref env, gas_left); + + // Increment nonce of sender AFTER computing the created address + // to use the correct nonce when computing the address. + sender_account.set_nonce(sender_account.nonce() + 1); + + env.state.set_account(sender_account); + (message, is_deploy_tx) + }; + + let mut summary = Self::process_message_call(message, env, is_deploy_tx); + + // Cancel the max_fee that was taken from the sender to prevent double charging + let mut sender_account = summary.state.get_account(origin.evm); + sender_account.set_balance(sender_account.balance() + max_fee.into()); + summary.state.set_account(sender_account); + + // Gas refunds + let gas_used = tx.gas_limit() - summary.gas_left; + let gas_refund = core::cmp::min(gas_used / 5, summary.gas_refund); + + // Charging gas fees to the sender + // At the end of the tx, the sender must have paid + // (gas_used - gas_refund) * gas_price to the miner + // Because tx.gas_price == env.gas_price, and we checked the sender has enough balance + // to cover the gas fees + the value transfer, this transfer should never fail. + // We can thus directly charge the sender for the effective gas fees, + // without pre-emtively charging for the tx gas fee and then refund. + // This is not true for EIP-1559 transactions - not supported yet. + let total_gas_used = gas_used - gas_refund; + let _transaction_fee = total_gas_used.into() * gas_price; + + //TODO(gas): EF-tests doesn't yet support in-EVM gas charging, they assume that the gas + //charged is always correct for now. + // As correct gas accounting is not an immediate priority, we can just ignore the gas + // charging for now. + // match summary + // .state + // .add_transfer( + // Transfer { + // sender: origin, + // recipient: Address { + // evm: coinbase, starknet: block_info.sequencer_address, + // }, + // amount: transaction_fee.into() + // } + // ) { + // Result::Ok(_) => {}, + // Result::Err(err) => { + // + // return TransactionResultTrait::exceptional_failure( + // err.to_bytes(), tx.gas_limit() + // ); + // } + // }; + + TransactionResult { + success: summary.status == ExecutionResultStatus::Success, + return_data: summary.return_data, + gas_used: total_gas_used, + state: summary.state, + } + } + + + fn process_message_call( + message: Message, mut env: Environment, is_deploy_tx: bool, + ) -> ExecutionSummary { + let mut target_account = env.state.get_account(message.target.evm); + let result = if is_deploy_tx { + // Check collision + if target_account.has_code_or_nonce() { + return ExecutionSummary { + status: ExecutionResultStatus::Exception, + return_data: EVMError::Collision.to_bytes(), + gas_left: 0, + state: env.state, + gas_refund: 0 + }; + } + + let mut result = Self::process_create_message(message, ref env); + if result.is_success() { + result.return_data = message.target.evm.to_bytes().span(); + } + result + } else { + Self::process_message(message, ref env) + }; + + // No need to take snapshot of state, as the state is still empty at this point. + ExecutionSummary { + status: result.status, + state: env.state, + return_data: result.return_data, + gas_left: result.gas_left, + gas_refund: result.gas_refund + } + } + + fn process_create_message(message: Message, ref env: Environment) -> ExecutionResult { + //TODO(optimization) - Since the effects of executed code are + //reverted in the `process_message` function already, + // we only need to revert the changes made to the target account. Take a + // snapshot of the environment state so that we can revert if the + let state_snapshot = env.state.clone(); + let target_evm_address = message.target.evm; + + //@dev: Adding a scope block around `target_account` to ensure that the same instance is not + //being accessed after the state has been modified in `process_message`. + { + let mut target_account = env.state.get_account(target_evm_address); + // Increment nonce of target + target_account.set_nonce(1); + // Set the target as created + target_account.set_created(true); + target_account.address = message.target; + env.state.set_account(target_account); + } + + let mut result = Self::process_message(message, ref env); + if result.is_success() { + // Write the return_data of the initcode + // as the deployed contract's bytecode and charge gas + let target_account = env.state.get_account(target_evm_address); + match result.finalize_creation(target_account) { + Result::Ok(account_created) => { env.state.set_account(account_created) }, + Result::Err(err) => { + env.state = state_snapshot; + result.return_data = [].span(); + return ExecutionResultTrait::exceptional_failure( + err.to_bytes(), result.accessed_addresses, result.accessed_storage_keys + ); + } + }; + } else { + // Revert state to the snapshot taken before the create processing. + env.state = state_snapshot; + } + result + } + + fn process_message(message: Message, ref env: Environment) -> ExecutionResult { + if (message.depth > constants::STACK_MAX_DEPTH) { + // Because the failure happens before any modification to warm address/storage, + // we can pass an empty set + + return ExecutionResultTrait::exceptional_failure( + EVMError::DepthLimit.to_bytes(), Default::default(), Default::default() + ); + } + + let state_snapshot = env.state.clone(); + if message.should_transfer_value && message.value != 0 { + let transfer = Transfer { + sender: message.caller, recipient: message.target, amount: message.value + }; + match env.state.add_transfer(transfer) { + Result::Ok(_) => {}, + Result::Err(err) => { + return ExecutionResultTrait::exceptional_failure( + err.to_bytes(), Default::default(), Default::default() + ); + } + } + } + + // Instantiate a new VM using the message to process and the current environment. + let mut vm: VM = VMTrait::new(message, env); + + // Decode and execute the current opcode. + // until we have processed all opcodes or until we have stopped. + // Use a recursive function to allow passing VM by ref - which wouldn't work in a loop; + let result = Self::execute_code(ref vm); + + // Retrieve ownership of the `env` variable + // The state in the environment has been modified by the VM. + env = vm.env; + + if !result.is_success() { + // The `process_message` function has mutated the environment state. + // Revert state changes using the old snapshot as execution failed. + + env.state = state_snapshot; + } + + result + } + + fn execute_code(ref vm: VM) -> ExecutionResult { + // Handle precompile logic + if vm.message.code_address.evm.is_precompile() { + let result = Precompiles::exec_precompile(ref vm); + + match result { + Result::Ok(_) => { + let status = if vm.is_error() { + ExecutionResultStatus::Revert + } else { + ExecutionResultStatus::Success + }; + return ExecutionResult { + status, + return_data: vm.return_data(), + gas_left: vm.gas_left(), + accessed_addresses: vm.accessed_addresses(), + accessed_storage_keys: vm.accessed_storage_keys(), + gas_refund: vm.gas_refund() + }; + }, + Result::Err(error) => { + // If an error occurred, revert execution self. + // Currently, revert reason is a Span. + return ExecutionResultTrait::exceptional_failure( + error.to_bytes(), vm.accessed_addresses(), vm.accessed_storage_keys() + ); + } + } + } + + // Retrieve the current program counter. + let pc = vm.pc(); + let bytecode = vm.message().code; + + // If PC is out of bounds, stop the VM + // Also empties the returndata - akin to executing the STOP opcode. + if pc >= bytecode.len() { + vm.exec_stop(); + } + + if !vm.is_running() { + // REVERT opcode case + if vm.is_error() { + return ExecutionResult { + status: ExecutionResultStatus::Revert, + return_data: vm.return_data(), + gas_left: vm.gas_left(), + accessed_addresses: vm.accessed_addresses(), + accessed_storage_keys: vm.accessed_storage_keys(), + gas_refund: 0 + }; + }; + // Success case + return ExecutionResult { + status: ExecutionResultStatus::Success, + return_data: vm.return_data(), + gas_left: vm.gas_left(), + accessed_addresses: vm.accessed_addresses(), + accessed_storage_keys: vm.accessed_storage_keys(), + gas_refund: vm.gas_refund() + }; + } + + let opcode: u8 = *bytecode.at(pc); + + match Self::execute_opcode(ref vm, opcode) { + Result::Ok(_) => { + if opcode != 0x56 && opcode != 0x57 { + // Increment pc if not a JUMP family opcode + vm.set_pc(vm.pc() + 1); + } + + if vm.is_running() { + return Self::execute_code(ref vm); + } + // REVERT opcode case + if vm.is_error() { + return ExecutionResult { + status: ExecutionResultStatus::Revert, + return_data: vm.return_data(), + gas_left: vm.gas_left(), + accessed_addresses: vm.accessed_addresses(), + accessed_storage_keys: vm.accessed_storage_keys(), + gas_refund: 0 + }; + }; + // Success case + return ExecutionResult { + status: ExecutionResultStatus::Success, + return_data: vm.return_data(), + gas_left: vm.gas_left(), + accessed_addresses: vm.accessed_addresses(), + accessed_storage_keys: vm.accessed_storage_keys(), + gas_refund: vm.gas_refund() + }; + }, + Result::Err(error) => { + // If an error occurred, revert execution self. + // Currently, revert reason is a Span. + return ExecutionResultTrait::exceptional_failure( + error.to_bytes(), vm.accessed_addresses(), vm.accessed_storage_keys() + ); + } + } + } + + fn execute_opcode(ref self: VM, opcode: u8) -> Result<(), EVMError> { + // Call the appropriate function based on the opcode. + if opcode == 0x00 { + // STOP + return Result::Ok(self.exec_stop()); + } + if opcode == 0x01 { + // ADD + return self.exec_add(); + } + if opcode == 0x02 { + // MUL + return self.exec_mul(); + } + if opcode == 0x03 { + // SUB + return self.exec_sub(); + } + if opcode == 0x04 { + // DIV + return self.exec_div(); + } + if opcode == 0x05 { + // SDIV + return self.exec_sdiv(); + } + if opcode == 0x06 { + // MOD + return self.exec_mod(); + } + if opcode == 0x07 { + // SMOD + return self.exec_smod(); + } + if opcode == 0x08 { + // ADDMOD + return self.exec_addmod(); + } + if opcode == 0x09 { + // MULMOD + return self.exec_mulmod(); + } + if opcode == 0x0A { + // EXP + return self.exec_exp(); + } + if opcode == 0x0B { + // SIGNEXTEND + return self.exec_signextend(); + } + if opcode == 0x10 { + // LT + return self.exec_lt(); + } + if opcode == 0x11 { + // GT + return self.exec_gt(); + } + if opcode == 0x12 { + // SLT + return self.exec_slt(); + } + if opcode == 0x13 { + // SGT + return self.exec_sgt(); + } + if opcode == 0x14 { + // EQ + return self.exec_eq(); + } + if opcode == 0x15 { + // ISZERO + return self.exec_iszero(); + } + if opcode == 0x16 { + // AND + return self.exec_and(); + } + if opcode == 0x17 { + // OR + return self.exec_or(); + } + if opcode == 0x18 { + // XOR + return self.exec_xor(); + } + if opcode == 0x19 { + // NOT + return self.exec_not(); + } + if opcode == 0x1A { + // BYTE + return self.exec_byte(); + } + if opcode == 0x1B { + // SHL + return self.exec_shl(); + } + if opcode == 0x1C { + // SHR + return self.exec_shr(); + } + if opcode == 0x1D { + // SAR + return self.exec_sar(); + } + if opcode == 0x20 { + // KECCAK256 + return self.exec_sha3(); + } + if opcode == 0x30 { + // ADDRESS + return self.exec_address(); + } + if opcode == 0x31 { + // BALANCE + return self.exec_balance(); + } + if opcode == 0x32 { + // ORIGIN + return self.exec_origin(); + } + if opcode == 0x33 { + // CALLER + return self.exec_caller(); + } + if opcode == 0x34 { + // CALLVALUE + return self.exec_callvalue(); + } + if opcode == 0x35 { + // CALLDATALOAD + return self.exec_calldataload(); + } + if opcode == 0x36 { + // CALLDATASIZE + return self.exec_calldatasize(); + } + if opcode == 0x37 { + // CALLDATACOPY + return self.exec_calldatacopy(); + } + if opcode == 0x38 { + // CODESIZE + return self.exec_codesize(); + } + if opcode == 0x39 { + // CODECOPY + return self.exec_codecopy(); + } + if opcode == 0x3A { + // GASPRICE + return self.exec_gasprice(); + } + if opcode == 0x3B { + // EXTCODESIZE + return self.exec_extcodesize(); + } + if opcode == 0x3C { + // EXTCODECOPY + return self.exec_extcodecopy(); + } + if opcode == 0x3D { + // RETURNDATASIZE + return self.exec_returndatasize(); + } + if opcode == 0x3E { + // RETURNDATACOPY + return self.exec_returndatacopy(); + } + if opcode == 0x3F { + // EXTCODEHASH + return self.exec_extcodehash(); + } + if opcode == 0x40 { + // BLOCKHASH + return self.exec_blockhash(); + } + if opcode == 0x41 { + // COINBASE + return self.exec_coinbase(); + } + if opcode == 0x42 { + // TIMESTAMP + return self.exec_timestamp(); + } + if opcode == 0x43 { + // NUMBER + return self.exec_number(); + } + if opcode == 0x44 { + // PREVRANDAO + return self.exec_prevrandao(); + } + if opcode == 0x45 { + // GASLIMIT + return self.exec_gaslimit(); + } + if opcode == 0x46 { + // CHAINID + return self.exec_chainid(); + } + if opcode == 0x47 { + // SELFBALANCE + return self.exec_selfbalance(); + } + if opcode == 0x48 { + // BASEFEE + return self.exec_basefee(); + } + if opcode == 0x49 { + // BLOBHASH + return self.exec_blobhash(); + } + if opcode == 0x4A { + // BLOBBASEFEE + return self.exec_blobbasefee(); + } + if opcode == 0x50 { + // POP + return self.exec_pop(); + } + if opcode == 0x51 { + // MLOAD + return self.exec_mload(); + } + if opcode == 0x52 { + // MSTORE + return self.exec_mstore(); + } + if opcode == 0x53 { + // MSTORE8 + return self.exec_mstore8(); + } + if opcode == 0x54 { + // SLOAD + return self.exec_sload(); + } + if opcode == 0x55 { + // SSTORE + return self.exec_sstore(); + } + if opcode == 0x56 { + // JUMP + return self.exec_jump(); + } + if opcode == 0x57 { + // JUMPI + return self.exec_jumpi(); + } + if opcode == 0x58 { + // PC + return self.exec_pc(); + } + if opcode == 0x59 { + // MSIZE + return self.exec_msize(); + } + if opcode == 0x5A { + // GAS + return self.exec_gas(); + } + if opcode == 0x5B { + // JUMPDEST + return self.exec_jumpdest(); + } + if opcode == 0x5C { + // TLOAD + return self.exec_tload(); + } + if opcode == 0x5D { + // TSTORE + return self.exec_tstore(); + } + if opcode == 0x5E { + // MCOPY + return self.exec_mcopy(); + } + if opcode == 0x5F { + // PUSH0 + return self.exec_push0(); + } + if opcode == 0x60 { + // PUSH1 + return self.exec_push1(); + } + if opcode == 0x61 { + // PUSH2 + return self.exec_push2(); + } + if opcode == 0x62 { + // PUSH3 + return self.exec_push3(); + } + if opcode == 0x63 { + // PUSH4 + return self.exec_push4(); + } + if opcode == 0x64 { + // PUSH5 + return self.exec_push5(); + } + if opcode == 0x65 { + // PUSH6 + return self.exec_push6(); + } + if opcode == 0x66 { + // PUSH7 + return self.exec_push7(); + } + if opcode == 0x67 { + // PUSH8 + return self.exec_push8(); + } + if opcode == 0x68 { + // PUSH9 + return self.exec_push9(); + } + if opcode == 0x69 { + // PUSH10 + return self.exec_push10(); + } + if opcode == 0x6A { + // PUSH11 + return self.exec_push11(); + } + if opcode == 0x6B { + // PUSH12 + return self.exec_push12(); + } + if opcode == 0x6C { + // PUSH13 + return self.exec_push13(); + } + if opcode == 0x6D { + // PUSH14 + return self.exec_push14(); + } + if opcode == 0x6E { + // PUSH15 + return self.exec_push15(); + } + if opcode == 0x6F { + // PUSH16 + return self.exec_push16(); + } + if opcode == 0x70 { + // PUSH17 + return self.exec_push17(); + } + if opcode == 0x71 { + // PUSH18 + return self.exec_push18(); + } + if opcode == 0x72 { + // PUSH19 + return self.exec_push19(); + } + if opcode == 0x73 { + // PUSH20 + return self.exec_push20(); + } + if opcode == 0x74 { + // PUSH21 + return self.exec_push21(); + } + if opcode == 0x75 { + // PUSH22 + return self.exec_push22(); + } + if opcode == 0x76 { + // PUSH23 + return self.exec_push23(); + } + if opcode == 0x77 { + // PUSH24 + return self.exec_push24(); + } + if opcode == 0x78 { + // PUSH25 + return self.exec_push25(); + } + if opcode == 0x79 { + // PUSH26 + return self.exec_push26(); + } + if opcode == 0x7A { + // PUSH27 + return self.exec_push27(); + } + if opcode == 0x7B { + // PUSH28 + return self.exec_push28(); + } + if opcode == 0x7C { + // PUSH29 + return self.exec_push29(); + } + if opcode == 0x7D { + // PUSH30 + return self.exec_push30(); + } + if opcode == 0x7E { + // PUSH31 + return self.exec_push31(); + } + if opcode == 0x7F { + // PUSH32 + return self.exec_push32(); + } + if opcode == 0x80 { + // DUP1 + return self.exec_dup1(); + } + if opcode == 0x81 { + // DUP2 + return self.exec_dup2(); + } + if opcode == 0x82 { + // DUP3 + return self.exec_dup3(); + } + if opcode == 0x83 { + // DUP4 + return self.exec_dup4(); + } + if opcode == 0x84 { + // DUP5 + return self.exec_dup5(); + } + if opcode == 0x85 { + // DUP6 + return self.exec_dup6(); + } + if opcode == 0x86 { + // DUP7 + return self.exec_dup7(); + } + if opcode == 0x87 { + // DUP8 + return self.exec_dup8(); + } + if opcode == 0x88 { + // DUP9 + return self.exec_dup9(); + } + if opcode == 0x89 { + // DUP10 + return self.exec_dup10(); + } + if opcode == 0x8A { + // DUP11 + return self.exec_dup11(); + } + if opcode == 0x8B { + // DUP12 + return self.exec_dup12(); + } + if opcode == 0x8C { + // DUP13 + return self.exec_dup13(); + } + if opcode == 0x8D { + // DUP14 + return self.exec_dup14(); + } + if opcode == 0x8E { + // DUP15 + return self.exec_dup15(); + } + if opcode == 0x8F { + // DUP16 + return self.exec_dup16(); + } + if opcode == 0x90 { + // SWAP1 + return self.exec_swap1(); + } + if opcode == 0x91 { + // SWAP2 + return self.exec_swap2(); + } + if opcode == 0x92 { + // SWAP3 + return self.exec_swap3(); + } + if opcode == 0x93 { + // SWAP4 + return self.exec_swap4(); + } + if opcode == 0x94 { + // SWAP5 + return self.exec_swap5(); + } + if opcode == 0x95 { + // SWAP6 + return self.exec_swap6(); + } + if opcode == 0x96 { + // SWAP7 + return self.exec_swap7(); + } + if opcode == 0x97 { + // SWAP8 + return self.exec_swap8(); + } + if opcode == 0x98 { + // SWAP9 + return self.exec_swap9(); + } + if opcode == 0x99 { + // SWAP10 + return self.exec_swap10(); + } + if opcode == 0x9A { + // SWAP11 + return self.exec_swap11(); + } + if opcode == 0x9B { + // SWAP12 + return self.exec_swap12(); + } + if opcode == 0x9C { + // SWAP13 + return self.exec_swap13(); + } + if opcode == 0x9D { + // SWAP14 + return self.exec_swap14(); + } + if opcode == 0x9E { + // SWAP15 + return self.exec_swap15(); + } + if opcode == 0x9F { + // SWAP16 + return self.exec_swap16(); + } + if opcode == 0xA0 { + // LOG0 + return self.exec_log0(); + } + if opcode == 0xA1 { + // LOG1 + return self.exec_log1(); + } + if opcode == 0xA2 { + // LOG2 + return self.exec_log2(); + } + if opcode == 0xA3 { + // LOG3 + return self.exec_log3(); + } + if opcode == 0xA4 { + // LOG4 + return self.exec_log4(); + } + if opcode == 0xF0 { + // CREATE + return self.exec_create(); + } + if opcode == 0xF1 { + // CALL + return self.exec_call(); + } + if opcode == 0xF2 { + // CALLCODE + return self.exec_callcode(); + } + if opcode == 0xF3 { + // RETURN + return self.exec_return(); + } + if opcode == 0xF4 { + // DELEGATECALL + return self.exec_delegatecall(); + } + if opcode == 0xF5 { + // CREATE2 + return self.exec_create2(); + } + if opcode == 0xFA { + // STATICCALL + return self.exec_staticcall(); + } + if opcode == 0xFD { + // REVERT + return self.exec_revert(); + } + if opcode == 0xFE { + // INVALID + return self.exec_invalid(); + } + if opcode == 0xFF { + // SELFDESTRUCT + return self.exec_selfdestruct(); + } + // Unknown opcode + return Result::Err(EVMError::InvalidOpcode(opcode)); + } +} + +#[cfg(test)] +mod tests { + use contracts::kakarot_core::KakarotCore; + use core::num::traits::Zero; + use crate::model::{Account, Environment, Message}; + use crate::state::StateTrait; + use crate::test_utils::{dual_origin, test_dual_address}; + use super::EVMTrait; + use utils::constants::EMPTY_KECCAK; + use utils::eth_transaction::common::TxKind; + use utils::eth_transaction::legacy::TxLegacy; + use utils::eth_transaction::transaction::{Transaction, TransactionTrait}; + + fn setup() -> (KakarotCore::ContractState, Account, Environment) { + let state = KakarotCore::contract_state_for_testing(); + let sender_account = Account { + address: test_dual_address(), + nonce: 5, + balance: 1000000000000000000_u256, // 1 ETH + code: array![].span(), + code_hash: EMPTY_KECCAK, + selfdestruct: false, + is_created: true, + }; + let mut env = Environment { + origin: dual_origin(), + gas_price: 20000000000_u128, // 20 Gwei + chain_id: 1_u64, + prevrandao: 0_u256, + block_number: 12345_u64, + block_gas_limit: 30000000_u64, + block_timestamp: 1634567890_u64, + coinbase: 0x0000000000000000000000000000000000000000.try_into().unwrap(), + base_fee: 0_u64, + state: Default::default(), + }; + env.state.set_account(sender_account); + (state, sender_account, env) + } + + #[test] + fn test_prepare_message_create() { + let (mut state, sender_account, mut env) = setup(); + let tx = Transaction::Legacy( + TxLegacy { + chain_id: Option::Some(1), + nonce: 5, + gas_price: 20000000000_u128, // 20 Gwei + gas_limit: 1000000_u64, + to: TxKind::Create, + value: 0_u256, + input: array![0x60, 0x80, 0x60, 0x40, 0x52].span(), // Simple contract bytecode + } + ); + + let (message, is_deploy_tx) = state + .prepare_message(@tx, @sender_account, ref env, tx.gas_limit()); + + assert_eq!(is_deploy_tx, true); + assert_eq!(message.code, tx.input()); + assert_eq!( + message.target.evm, 0xf50541960eec6df5caa295adee1a1a95c3c3241c.try_into().unwrap() + ); // compute_contract_address('evm_address', 5); + assert_eq!(message.code_address, Zero::zero()); + assert_eq!(message.data, [].span()); + assert_eq!(message.gas_limit, tx.gas_limit()); + assert_eq!(message.depth, 0); + assert_eq!(message.should_transfer_value, true); + assert_eq!(message.value, 0_u256); + } + + #[test] + fn test_prepare_message_call() { + let (mut state, sender_account, mut env) = setup(); + let target_address = sender_account.address; + let tx = Transaction::Legacy( + TxLegacy { + chain_id: Option::Some(1), + nonce: 5, + gas_price: 20000000000_u128, // 20 Gwei + gas_limit: 1000000_u64, + to: TxKind::Call(target_address.evm), + value: 1000000000000000000_u256, // 1 ETH + input: array![0x12, 0x34, 0x56, 0x78].span(), // Some calldata + } + ); + + let (message, is_deploy_tx) = state + .prepare_message(@tx, @sender_account, ref env, tx.gas_limit()); + + assert_eq!(is_deploy_tx, false); + assert_eq!(message.target.evm, target_address.evm); + assert_eq!(message.code_address.evm, target_address.evm); + assert_eq!(message.code, sender_account.code); + assert_eq!(message.data, tx.input()); + assert_eq!(message.gas_limit, tx.gas_limit()); + assert_eq!(message.depth, 0); + assert_eq!(message.should_transfer_value, true); + assert_eq!(message.value, 1000000000000000000_u256); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/lib.cairo b/cairo/kakarot-ssj/crates/evm/src/lib.cairo new file mode 100644 index 000000000..dc80be6c3 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/lib.cairo @@ -0,0 +1,40 @@ +pub mod backend; +// Call opcodes helpers +mod call_helpers; + +// Create opcodes helpers +mod create_helpers; + +// Errors module +pub mod errors; + +// Gas module +pub mod gas; + +// instructions module +pub mod instructions; + +// interpreter module +mod interpreter; + +// Memory module +mod memory; + +// Data Models module +pub mod model; + +// instructions module +pub mod precompiles; + +// Stack module +mod stack; + +// Local state +pub mod state; + +#[cfg(target: 'test')] +pub mod test_data; + +#[cfg(target: 'test')] +pub mod test_utils; +pub use interpreter::EVMTrait; diff --git a/cairo/kakarot-ssj/crates/evm/src/memory.cairo b/cairo/kakarot-ssj/crates/evm/src/memory.cairo new file mode 100644 index 000000000..fb3fc11ac --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/memory.cairo @@ -0,0 +1,1199 @@ +use core::cmp::min; +use core::dict::{Felt252Dict, Felt252DictTrait}; +use core::integer::{u32_safe_divmod}; +use utils::constants::{ + POW_2_0, POW_2_8, POW_2_16, POW_2_24, POW_2_32, POW_2_40, POW_2_48, POW_2_56, POW_2_64, + POW_2_72, POW_2_80, POW_2_88, POW_2_96, POW_2_104, POW_2_112, POW_2_120, POW_256_16 +}; +use utils::traits::array::ArrayExtTrait; +use utils::traits::bytes::FromBytes; +use utils::{helpers, math::Bitshift}; + +#[derive(Destruct, Default)] +pub struct Memory { + items: Felt252Dict, + bytes_len: usize, +} + +pub trait MemoryTrait { + fn new() -> Memory; + fn size(self: @Memory) -> usize; + fn store(ref self: Memory, element: u256, offset: usize); + fn store_byte(ref self: Memory, value: u8, offset: usize); + fn store_n(ref self: Memory, elements: Span, offset: usize); + fn store_padded_segment(ref self: Memory, offset: usize, length: usize, source: Span); + fn ensure_length(ref self: Memory, length: usize); + fn load(ref self: Memory, offset: usize) -> u256; + fn load_n(ref self: Memory, elements_len: usize, ref elements: Array, offset: usize); + fn copy(ref self: Memory, size: usize, source_offset: usize, dest_offset: usize); +} + +impl MemoryImpl of MemoryTrait { + /// Initializes a new `Memory` instance. + #[inline(always)] + fn new() -> Memory { + Memory { items: Default::default(), bytes_len: Default::default() } + } + /// Returns the size of the memory. + #[inline(always)] + fn size(self: @Memory) -> usize { + *self.bytes_len + } + + /// Stores a 32-bytes element into the memory. + /// + /// If the offset is aligned with the 16-bytes words in memory, the element is stored directly. + /// Otherwise, the element is split and stored in multiple words. + #[inline(always)] + fn store(ref self: Memory, element: u256, offset: usize) { + let nonzero_16: NonZero = 16_u32.try_into().unwrap(); + + // Check alignment of offset to bytes16 chunks + let (chunk_index, offset_in_chunk) = u32_safe_divmod(offset, nonzero_16); + + if offset_in_chunk == 0 { + // Offset is aligned. This is the simplest and most efficient case, + // so we optimize for it. + self.items.store_u256(element, chunk_index); + return (); + } + + // Offset is misaligned. + // | W0 | W1 | w2 | + // | EL_H | EL_L | + // ^---^ + // |-- mask = 256 ** offset_in_chunk + + self.store_element(element, chunk_index, offset_in_chunk); + } + + + /// Stores a single byte into memory at a specified offset. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance to store the byte in. + /// * `value` - The byte value to store in memory. + /// * `offset` - The offset within memory to store the byte at. + #[inline(always)] + fn store_byte(ref self: Memory, value: u8, offset: usize) { + let nonzero_16: NonZero = 16_u32.try_into().unwrap(); + + // Get offset's memory word index and left-based offset of byte in word. + let (chunk_index, left_offset) = u32_safe_divmod(offset, nonzero_16); + + // As the memory words are in big-endian order, we need to convert our left-based offset + // to a right-based one. + let right_offset = 15 - left_offset; + let mask: u128 = 0xFF * helpers::pow2(right_offset.into() * 8); + + // First erase byte value at offset, then set the new value using bitwise ops + let word: u128 = self.items.get(chunk_index.into()); + let new_word = (word & ~mask) | (value.into().shl(right_offset * 8)); + self.items.insert(chunk_index.into(), new_word); + } + + + /// Stores a span of N bytes into memory at a specified offset. + /// + /// This function checks the alignment of the offset to 16-byte chunks, and handles the special + /// case where the bytes to be stored are within the same word in memory using the + /// `store_bytes_in_single_chunk` function. If the bytes span multiple words, the function + /// stores the first word using the `store_first_word` function, the aligned words using the + /// `store_aligned_words` function, and the last word using the `store_last_word` function. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance to store the bytes in. + /// * `elements` - A span of bytes to store in memory. + /// * `offset` - The offset within memory to store the bytes at. + #[inline(always)] + fn store_n(ref self: Memory, elements: Span, offset: usize) { + if elements.len() == 0 { + return; + } + + let nonzero_16: NonZero = 16_u32.try_into().unwrap(); + + // Compute the offset inside the Memory, given its active segment, following the formula: + // index = offset + self.active_segment * 125000 + + // Check alignment of offset to bytes16 chunks. + let (initial_chunk, offset_in_chunk_i) = u32_safe_divmod(offset, nonzero_16); + let (final_chunk, mut offset_in_chunk_f) = u32_safe_divmod( + offset + elements.len() - 1, nonzero_16 + ); + offset_in_chunk_f += 1; + let mask_i: u256 = helpers::pow256_rev(offset_in_chunk_i); + let mask_f: u256 = helpers::pow256_rev(offset_in_chunk_f); + + // Special case: the bytes are stored within the same word. + if initial_chunk == final_chunk { + self.store_bytes_in_single_chunk(initial_chunk, mask_i, mask_f, elements); + return (); + } + + // Otherwise, fill first word. + self.store_first_word(initial_chunk, offset_in_chunk_i, mask_i, elements); + + // Store aligned bytes in [initial_chunk + 1, final_chunk - 1]. + // If initial_chunk + 1 == final_chunk, this will store nothing. + if initial_chunk + 1 != final_chunk { + let aligned_bytes = elements + .slice( + 16 - offset_in_chunk_i, + elements.len() - (16 - offset_in_chunk_i) - offset_in_chunk_f, + ); + self.store_aligned_words(initial_chunk + 1, aligned_bytes); + } + + let final_bytes = elements.slice(elements.len() - offset_in_chunk_f, offset_in_chunk_f); + self.store_last_word(final_chunk, offset_in_chunk_f, mask_f, final_bytes); + } + + /// Stores a span of N bytes into memory at a specified offset with padded with 0s to match the + /// size parameter. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance to store the bytes in. + /// * `offset` - The offset within memory to store the bytes at. + /// * `length` - The length of bytes to store in memory. + /// * `source` - A span of bytes to store in memory. + #[inline(always)] + fn store_padded_segment(ref self: Memory, offset: usize, length: usize, source: Span) { + if length == 0 { + return; + } + + // For performance reasons, we don't add the zeros directly to the source, which would + // generate an implicit copy, which might be expensive if the source is big. + // Instead, we'll copy the source into memory, then create a new span containing the zeros. + // TODO: optimize this with a specific function + let mut slice_size = min(source.len(), length); + + let data_to_copy: Span = source.slice(0, slice_size); + self.store_n(data_to_copy, offset); + // For out of bound bytes, 0s will be copied. + if (length > source.len()) { + let mut out_of_bounds_bytes: Array = ArrayTrait::new(); + out_of_bounds_bytes.append_n(0, length - source.len()); + + self.store_n(out_of_bounds_bytes.span(), offset + slice_size); + } + } + + /// Ensures that the memory is at least `length` bytes long. Expands if necessary. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance. + /// * `length` - The desired minimum length of the memory. + #[inline(always)] + fn ensure_length(ref self: Memory, length: usize) { + if self.size() < length { + self.expand(length - self.size()) + } else { + return; + } + } + + /// Expands memory if necessary, then load 32 bytes from it at the given offset. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance. + /// * `offset` - The offset within memory to load from. + /// + /// # Returns + /// + /// * `u256` - The loaded value. + #[inline(always)] + fn load(ref self: Memory, offset: usize) -> u256 { + self.load_internal(offset) + } + + /// Expands memory if necessary, then load elements_len bytes from the memory at given offset + /// inside elements. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance. + /// * `elements_len` - The number of bytes to load. + /// * `elements` - A mutable reference to the array to store the loaded bytes. + /// * `offset` - The offset within memory to load from. + #[inline(always)] + fn load_n(ref self: Memory, elements_len: usize, ref elements: Array, offset: usize) { + self.load_n_internal(elements_len, ref elements, offset); + } + + /// Copies a segment of memory from the source offset to the destination offset. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance. + /// * `size` - The number of bytes to copy. + /// * `source_offset` - The offset to copy from. + /// * `dest_offset` - The offset to copy to. + #[inline(always)] + fn copy(ref self: Memory, size: usize, source_offset: usize, dest_offset: usize) { + let mut data: Array = Default::default(); + self.load_n(size, ref data, source_offset); + self.store_n(data.span(), dest_offset); + } +} + +#[generate_trait] +pub(crate) impl InternalMemoryMethods of InternalMemoryTrait { + /// Stores a `u256` element at a specified offset within a memory chunk. + /// + /// It first computes the + /// masks for the high and low parts of the element, then splits the `u256` element into high + /// and low parts, and computes the new words to write to memory using the masks and the high + /// and low parts of the element. Finally, it writes the new words to memory. + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to store the element in. + /// * `element` - The `u256` element to store in memory. + /// * `chunk_index` - The index of the memory chunk to start storing the element in. + /// * `offset_in_chunk` - The offset within the memory chunk to store the element at. + #[inline(always)] + fn store_element(ref self: Memory, element: u256, chunk_index: usize, offset_in_chunk: u32) { + let mask: u256 = helpers::pow256_rev(offset_in_chunk); + let mask_c: u256 = POW_256_16 / mask; + + // Split the 2 input bytes16 chunks at offset_in_chunk. + let nonzero_mask_c: NonZero = mask_c.try_into().unwrap(); + let (el_hh, el_hl) = DivRem::div_rem(element.high.into(), nonzero_mask_c); + let (el_lh, el_ll) = DivRem::div_rem(element.low.into(), nonzero_mask_c); + + // Read the words at chunk_index, chunk_index + 2. + let w0: u128 = self.items.get(chunk_index.into()); + let w2: u128 = self.items.get(chunk_index.into() + 2); + + // Compute the new words + let w0_h: u256 = (w0.into() / mask); + let w2_l: u256 = (w2.into() / mask); + + // We can convert them back to felt252 as we know they fit in one word. + let new_w0: u128 = (w0_h.into() * mask + el_hh).try_into().unwrap(); + let new_w1: u128 = (el_hl.into() * mask + el_lh).try_into().unwrap(); + let new_w2: u128 = (el_ll.into() * mask + w2_l).try_into().unwrap(); + + // Write the new words + self.items.insert(chunk_index.into(), new_w0); + self.items.insert(chunk_index.into() + 1, new_w1); + self.items.insert(chunk_index.into() + 2, new_w2); + } + + /// Stores a span of bytes into a single memory chunk. + /// + /// This function computes a new word to be stored by combining the existing word with the new + /// bytes. + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to store the bytes in. + /// * `initial_chunk` - The index of the initial memory chunk to store the bytes in. + /// * `mask_i` - The mask for the high part of the word. + /// * `mask_f` - The mask for the low part of the word. + /// * `elements` - A span of bytes to store in memory. + #[inline(always)] + fn store_bytes_in_single_chunk( + ref self: Memory, initial_chunk: usize, mask_i: u256, mask_f: u256, elements: Span + ) { + let word: u128 = self.items.get(initial_chunk.into()); + let nonzero_mask_i: NonZero = mask_i.try_into().unwrap(); + let nonzero_mask_f: NonZero = mask_f.try_into().unwrap(); + let (word_high, word_low) = DivRem::div_rem(word.into(), nonzero_mask_i); + let (_, word_low_l) = DivRem::div_rem(word_low, nonzero_mask_f); + let bytes_as_word: u128 = elements + .slice(0, elements.len()) + .from_be_bytes_partial() + .expect('Failed to parse word_low'); + let new_w: u128 = (word_high * mask_i + bytes_as_word.into() * mask_f + word_low_l) + .try_into() + .unwrap(); + self.items.insert(initial_chunk.into(), new_w); + } + + /// Stores a sequence of bytes into memory in chunks of 16 bytes each. + /// + /// It combines each byte in the span into a single 16-byte value in big-endian order, + /// and stores this value in memory. The function then updates + /// the chunk index and slices the byte span to the next 16 bytes until all chunks have been + /// stored. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance to store the bytes in. + /// * `chunk_index` - The index of the chunk to start storing at. + /// * `elements` - A span of bytes to store in memory. + fn store_aligned_words(ref self: Memory, mut chunk_index: usize, mut elements: Span) { + while let Option::Some(words) = elements.multi_pop_front::<16>() { + let words = (*words).unbox().span(); + let current: u128 = ((*words[0]).into() * POW_2_120 + + (*words[1]).into() * POW_2_112 + + (*words[2]).into() * POW_2_104 + + (*words[3]).into() * POW_2_96 + + (*words[4]).into() * POW_2_88 + + (*words[5]).into() * POW_2_80 + + (*words[6]).into() * POW_2_72 + + (*words[7]).into() * POW_2_64 + + (*words[8]).into() * POW_2_56 + + (*words[9]).into() * POW_2_48 + + (*words[10]).into() * POW_2_40 + + (*words[11]).into() * POW_2_32 + + (*words[12]).into() * POW_2_24 + + (*words[13]).into() * POW_2_16 + + (*words[14]).into() * POW_2_8 + + (*words[15]).into() * POW_2_0); + + self.items.insert(chunk_index.into(), current); + chunk_index += 1; + } + } + + /// Retrieves aligned values from the memory structure, converts them back into a bytes array, + /// and appends them to the `elements` array. + /// + /// It iterates + /// over the chunks between the first and last chunk indices, retrieves the `u128` values from + /// the memory chunk, and splits them into big-endian byte arrays and concatenates using the + /// `split_word_128` function. + /// The results are concatenated to the `elements` array. + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to load the values from. + /// * `chunk_index` - The index of the first chunk to load from. + /// * `final_chunk` - The index of the last chunk to load from. + /// * `elements` - A reference to the byte array to append the loaded bytes to. + fn load_aligned_words( + ref self: Memory, mut chunk_index: usize, final_chunk: usize, ref elements: Array + ) { + for i in chunk_index + ..final_chunk { + let value = self.items.get(i.into()); + // Pushes 16 items to `elements` + helpers::split_word_128(value.into(), ref elements); + }; + } + + /// Loads a `u256` element from the memory chunk at a specified offset. + /// + /// If the offset is aligned with the memory words, the function returns the `u256` element at + /// the specified offset directly from the memory chunk. If the offset is misaligned, the + /// function computes the masks for the high and low parts of the first and last words of the + /// `u256` element, reads the words at the specified offset and the next two offsets, and + /// computes the high and low parts of the `u256` element using the masks and the read words. + /// The resulting `u256` element is then returned. + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to load the element from. + /// * `offset` - The offset within the memory chunk to load the element from. + /// + /// # Returns + /// + /// The `u256` element at the specified offset in the memory chunk. + #[inline(always)] + fn load_internal(ref self: Memory, offset: usize) -> u256 { + // Compute the offset inside the dict, given its active segment, following the formula: + // index = offset + self.active_segment * 125000 + let nonzero_16: NonZero = 16_u32.try_into().unwrap(); + let (chunk_index, offset_in_chunk) = u32_safe_divmod(offset, nonzero_16); + + if offset_in_chunk == 0 { + // Offset is aligned. This is the simplest and most efficient case, + // so we optimize for it. Note that no locals were allocated at all. + return self.items.read_u256(chunk_index); + } + + // Offset is misaligned. + // | W0 | W1 | w2 | + // | EL_H | EL_L | + // ^---^ + // |-- mask = 256 ** offset_in_chunk + + // Compute mask. + + let mask: u256 = helpers::pow256_rev(offset_in_chunk); + let mask_c: u256 = POW_256_16 / mask; + + // Read the words at chunk_index, +1, +2. + let w0: u128 = self.items.get(chunk_index.into()); + let w1: u128 = self.items.get(chunk_index.into() + 1); + let w2: u128 = self.items.get(chunk_index.into() + 2); + + // Compute element words + let w0_l: u256 = w0.into() % mask; + let nonzero_mask: NonZero = mask.try_into().unwrap(); + let (w1_h, w1_l): (u256, u256) = DivRem::div_rem(w1.into(), nonzero_mask); + let w2_h: u256 = w2.into() / mask; + let el_h: u128 = (w0_l * mask_c + w1_h).try_into().unwrap(); + let el_l: u128 = (w1_l * mask_c + w2_h).try_into().unwrap(); + + u256 { low: el_l, high: el_h } + } + + /// Loads a span of bytes from the memory chunk at a specified offset. + /// + /// This function loads the n bytes from the memory chunks, and splits the first word, + /// the aligned words, and the last word into bytes using the masks, and stored in + /// the parameter `elements` array. + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to load the bytes from. + /// * `elements_len` - The length of the array of bytes to load. + /// * `elements` - A reference to the array of bytes to load. + /// * `offset` - The chunk memory offset to load the bytes from. + #[inline(always)] + fn load_n_internal( + ref self: Memory, elements_len: usize, ref elements: Array, offset: usize + ) { + if elements_len == 0 { + return; + } + + let nonzero_16: NonZero = 16_u32.try_into().unwrap(); + + // Compute the offset inside the Memory, given its active segment, following the formula: + // index = offset + self.active_segment * 125000 + + // Check alignment of offset to bytes16 chunks. + let (initial_chunk, offset_in_chunk_i) = u32_safe_divmod(offset, nonzero_16); + let (final_chunk, mut offset_in_chunk_f) = u32_safe_divmod( + offset + elements_len - 1, nonzero_16 + ); + offset_in_chunk_f += 1; + let mask_i: u256 = helpers::pow256_rev(offset_in_chunk_i); + let mask_f: u256 = helpers::pow256_rev(offset_in_chunk_f); + + // Special case: within the same word. + if initial_chunk == final_chunk { + let w: u128 = self.items.get(initial_chunk.into()); + let w_l = w.into() % mask_i; + let w_lh = w_l / mask_f; + helpers::split_word(w_lh, elements_len, ref elements); + return; + } + + // Otherwise. + // Get first word. + let w_i = self.items.get(initial_chunk.into()); + let w_i_l = (w_i.into() % mask_i); + let _elements_first_word = helpers::split_word(w_i_l, 16 - offset_in_chunk_i, ref elements); + + // Get blocks. + self.load_aligned_words(initial_chunk + 1, final_chunk, ref elements); + + // Get last word. + let w_f = self.items.get(final_chunk.into()); + let w_f_h = w_f.into() / mask_f; + //TODO investigate why these two variables are not used + let _elements_last_word = helpers::split_word(w_f_h, offset_in_chunk_f, ref elements); + } + + + /// Expands the memory by a specified length + /// + /// The function updates the `bytes_len` field of the `Memory` instance to reflect the new size + /// of the memory chunk, + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to expand. + /// * `length` - The length to expand the memory chunk by. + #[inline(always)] + fn expand(ref self: Memory, length: usize) { + if (length == 0) { + return; + } + + let adjusted_length = (((length + 31) / 32) * 32); + let new_bytes_len = self.size() + adjusted_length; + + // Update memory size. + self.bytes_len = new_bytes_len; + } + + + /// Stores the first word of a span of bytes in the memory chunk at a specified offset. + /// The function computes the high part of the word by dividing the current word at the + /// specified offset by the mask, and computes the low part of the word by loading the remaining + /// bytes from the span of bytes. It then combines the high and low parts of the word using the + /// mask and stores the resulting word in the memory chunk at the specified offset. + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to store the word in. + /// * `chunk_index` - The index of the memory chunk to store the word in. + /// * `start_offset_in_chunk` - The offset within the chunk to store the word at. + /// * `start_mask` - The mask for the high part of the word. + /// * `elements` - A span of bytes to store. + /// + /// # Panics + /// + /// This function panics if the resulting word cannot be converted to a `u128` - which should + /// never happen. + #[inline(always)] + fn store_first_word( + ref self: Memory, + chunk_index: usize, + start_offset_in_chunk: usize, + start_mask: u256, + elements: Span + ) { + let word = self.items.get(chunk_index.into()); + let word_high = (word.into() / start_mask); + + let bytes_to_read = 16 - start_offset_in_chunk; + + let word_low: u128 = elements + .slice(0, bytes_to_read) + .from_be_bytes_partial() + .expect('Failed to parse word_low'); + + let new_word: u128 = (word_high * start_mask + word_low.into()).try_into().unwrap(); + self.items.insert(chunk_index.into(), new_word); + } + + /// Stores the last word of a span of bytes in the memory chunk at a specified offset. + /// The function computes the low part of the word by taking the current word at the specified + /// offset modulo the mask, and computes the high part of the word by loading the remaining + /// bytes from the span of bytes. It then combines the high and low parts of the word using the + /// mask and stores the resulting word in the memory chunk at the specified offset. + /// + /// # Arguments + /// + /// * `self` - A reference to the `Memory` instance to store the word in. + /// * `chunk_index` - The index of the memory chunk to store the word in. + /// * `end_offset_in_chunk` - The offset within the chunk to store the word at. + /// * `end_mask` - The mask for the low part of the word. + /// * `elements` - A span of bytes to store. + /// + /// # Panics + /// + /// This function panics if the resulting word cannot be converted to a `u128` - which should + /// never happen. + #[inline(always)] + fn store_last_word( + ref self: Memory, + chunk_index: usize, + end_offset_in_chunk: usize, + end_mask: u256, + elements: Span + ) { + let word = self.items.get(chunk_index.into()); + let word_low = (word.into() % end_mask); + + let low_bytes: u128 = elements + .slice(0, end_offset_in_chunk) + .from_be_bytes_partial() + .expect('Failed to parse low_bytes'); + let new_word: u128 = (low_bytes.into() * end_mask + word_low).try_into().unwrap(); + self.items.insert(chunk_index.into(), new_word); + } +} + +#[generate_trait] +impl Felt252DictExtensionImpl of Felt252DictExtension { + /// Stores a u256 element into the dictionary. + /// The element will be stored as two distinct u128 elements, + /// thus taking two indexes. + /// + /// # Arguments + /// * `self` - A mutable reference to the `Felt252Dict` instance. + /// * `element` - The element to store, of type `u256`. + /// * `index` - The `usize` index at which to store the element. + #[inline(always)] + fn store_u256(ref self: Felt252Dict, element: u256, index: usize) { + let index: felt252 = index.into(); + self.insert(index, element.high.into()); + self.insert(index + 1, element.low.into()); + } + + /// Reads a u256 element from the dictionary. + /// The element is stored as two distinct u128 elements, + /// thus we have to read the low and high parts and combine them. + /// The memory is big-endian organized, so the high part is stored first. + /// + /// # Arguments + /// * `self` - A mutable reference to the `Felt252Dict` instance. + /// * `index` - The `usize` index at which the element is stored. + /// + /// # Returns + /// * The element read, of type `u256`. + #[inline(always)] + fn read_u256(ref self: Felt252Dict, index: usize) -> u256 { + let index: felt252 = index.into(); + let high: u128 = self.get(index); + let low: u128 = self.get(index + 1); + u256 { low: low, high: high } + } +} + + +#[cfg(test)] +mod tests { + use core::num::traits::Bounded; + use crate::memory::{MemoryTrait, InternalMemoryTrait}; + use utils::constants::{POW_2_8, POW_2_56, POW_2_64, POW_2_120}; + use utils::{ + math::Exponentiation, math::WrappingExponentiation, helpers, traits::array::SpanExtTrait + }; + + + fn load_should_load_an_element_from_the_memory_with_offset_stored_with_store_n( + offset: usize, low: u128, high: u128 + ) { + // Given + let mut memory = MemoryTrait::new(); + + let value: u256 = u256 { low: low, high: high }; + + let bytes_array = helpers::u256_to_bytes_array(value); + + memory.store_n(bytes_array.span(), offset); + + // When + let mut elements: Array = Default::default(); + memory.load_n_internal(32, ref elements, offset); + + // Then + assert(elements == bytes_array, 'result not matching expected'); + } + + fn load_should_load_an_element_from_the_memory_with_offset_stored_with_store( + offset: usize, low: u128, high: u128, active_segment: usize, + ) { + // Given + let mut memory = MemoryTrait::new(); + + let value: u256 = u256 { low: low, high: high }; + + memory.store(value, offset); + + // When + let result: u256 = memory.load_internal(offset); + + // Then + assert(result == value, 'result not matching expected'); + } + + + #[test] + fn test_init_should_return_an_empty_memory() { + // When + let mut result = MemoryTrait::new(); + + // Then + assert(result.size() == 0, 'memory not empty'); + } + + #[test] + fn test_len_should_return_the_length_of_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let result = memory.size(); + + // Then + assert(result == 0, 'memory not empty'); + } + + #[test] + fn test_store_should_add_an_element_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let value: u256 = 1; + memory.store(value, 0); + + // Then + assert_eq!(memory.items.get(0), 0); + assert_eq!(memory.items.get(1), 1); + } + + #[test] + fn test_store_should_add_an_element_with_offset_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let value: u256 = 1; + let offset = 1; + memory.store(value, offset); + + // Then + let internal_index = offset / 2; + assert_eq!(memory.items.get(internal_index.into()), 0); + assert_eq!(memory.items.get(internal_index.into() + 1), 0); + assert_eq!(memory.items.get(internal_index.into() + 2), 0x01000000000000000000000000000000); + } + + #[test] + fn test_store_should_add_n_elements_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let value: u256 = 1; + let offset = 0; + let bytes_array = helpers::u256_to_bytes_array(value); + memory.store_n(bytes_array.span(), offset); + + // Then + let internal_index = offset / 2; + assert_eq!(memory.items.get(internal_index.into()), 0); + assert_eq!(memory.items.get(internal_index.into() + 1), 1); + } + + + #[test] + fn test_store_n_no_aligned_words() { + let mut memory = MemoryTrait::new(); + let byte_offset = 15; + memory.store_n([1, 2].span(), byte_offset); + + let internal_index = byte_offset / 16; + assert_eq!(memory.items.get(internal_index.into()), 0x01); + assert_eq!(memory.items.get(internal_index.into() + 1), 0x02000000000000000000000000000000); + } + + #[test] + fn test_store_n_2_aligned_words() { + let mut memory = MemoryTrait::new(); + let bytes_arr = [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35 + ].span(); + memory.store_n(bytes_arr, 15); + // value [1], will be stored in first word, values [2:34] will be stored in aligned words, + // value [35] will be stored in final word + + let mut stored_bytes = Default::default(); + memory.load_n_internal(35, ref stored_bytes, 15); + assert(stored_bytes.span() == bytes_arr, 'stored bytes not == expected'); + } + + #[test] + fn test_load_n_internal_same_word() { + let mut memory = MemoryTrait::new(); + memory.store(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0); + + let mut results: Array = ArrayTrait::new(); + memory.load_n_internal(16, ref results, 0); + + assert(results.len() == 16, 'error'); + for result in results { + assert(result == 0xFF, 'byte value loaded not correct'); + } + } + + + #[test] + fn test_load_should_load_an_element_from_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + // In the memory, the following values are stored in the order 1, 2, 3, 4 (Big Endian) + let first_value: u256 = u256 { low: 2, high: 1 }; + let second_value = u256 { low: 4, high: 3 }; + let first_bytes_array = helpers::u256_to_bytes_array(first_value); + let second_bytes_array = helpers::u256_to_bytes_array(second_value); + memory.store_n(first_bytes_array.span(), 0); + + memory.store_n(second_bytes_array.span(), 32); + + // When + let result: u256 = memory.load_internal(0); + + // Then + assert(result == first_value, 'res not u256{2,1}'); + + // When + let result: u256 = memory.load_internal(32); + + // Then + assert(result == second_value, 'res not u256{4,3}'); + + // When + let result: u256 = memory.load_internal(16); + + // Then + assert(result == u256 { low: 3, high: 2 }, 'res not u256{3,2}'); + } + + #[test] + fn test_load_should_load_an_element_from_the_memory_with_offset_8() { + load_should_load_an_element_from_the_memory_with_offset_stored_with_store_n( + 8, 2 * POW_2_64, POW_2_64 + ); + } + #[test] + fn test_load_should_load_an_element_from_the_memory_with_offset_7() { + load_should_load_an_element_from_the_memory_with_offset_stored_with_store_n( + 7, 2 * POW_2_56, POW_2_56 + ); + } + #[test] + fn test_load_should_load_an_element_from_the_memory_with_offset_23() { + load_should_load_an_element_from_the_memory_with_offset_stored_with_store_n( + 23, 3 * POW_2_56, 2 * POW_2_56 + ); + } + + #[test] + fn test_load_should_load_an_element_from_the_memory_with_offset_33() { + load_should_load_an_element_from_the_memory_with_offset_stored_with_store_n( + 33, 4 * POW_2_8, 3 * POW_2_8 + ); + } + #[test] + fn test_load_should_load_an_element_from_the_memory_with_offset_63() { + load_should_load_an_element_from_the_memory_with_offset_stored_with_store_n( + 63, 0, 4 * POW_2_120 + ); + } + + #[test] + fn test_load_should_load_an_element_from_the_memory_with_offset_500() { + load_should_load_an_element_from_the_memory_with_offset_stored_with_store_n(500, 0, 0); + } + + + #[test] + fn test_expand__should_return_the_same_memory_and_no_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + memory.bytes_len = 32; + memory.store_n(bytes_array.span(), 0); + + // When + memory.expand(0); + + // Then + assert(memory.size() == 32, 'memory should be 32bytes'); + let value = memory.load_internal(0); + assert(value == 1, 'value should be 1'); + } + + #[test] + fn test_expand__should_return_expanded_memory_by_one_word() { + // Given + let mut memory = MemoryTrait::new(); + + // When + memory.expand(1); + + // Then + assert_eq!(memory.size(), 32); + } + + #[test] + fn test_expand__should_return_expanded_memory_by_exactly_one_word_and_cost() { + // Given + let mut memory = MemoryTrait::new(); + + // When + memory.expand(32); + + // Then + assert_eq!(memory.size(), 32); + } + + #[test] + fn test_expand__should_return_expanded_memory_by_two_words_and_cost() { + // Given + let mut memory = MemoryTrait::new(); + + // When + memory.expand(33); + + // Then + assert_eq!(memory.size(), 64); + } + + #[test] + fn test_ensure_length__should_return_the_same_memory_and_no_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + + memory.bytes_len = 32; + memory.store_n(bytes_array.span(), 0); + + // When + memory.ensure_length(1); + + // Then + assert_eq!(memory.size(), 32); + let value = memory.load_internal(0); + assert_eq!(value, 1); + } + + #[test] + fn test_ensure_length__should_return_expanded_memory_and_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + + memory.bytes_len = 32; + memory.store_n(bytes_array.span(), 0); + + // When + memory.ensure_length(33); + + // Then + assert_eq!(memory.size(), 64); + let value = memory.load_internal(0); + assert_eq!(value, 1); + } + + #[test] + fn test_load_should_return_element() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + memory.bytes_len = 32; + memory.store_n(bytes_array.span(), 0); + + // When + let value = memory.load(32); + + // Then + assert_eq!(value, 0); + } + + #[test] + fn test_store_padded_segment_should_not_change_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let bytes = [1, 2, 3, 4, 5].span(); + memory.store_padded_segment(0, 0, bytes); + + // Then + let item_0 = memory.items.get(0); + let item_1 = memory.items.get(1); + assert_eq!(item_0, 0); + assert_eq!(item_1, 0); + } + + #[test] + fn test_store_padded_segment_should_write_to_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let bytes = [].span(); + memory.store_padded_segment(10, 10, bytes); + + // Then + let word = memory.load(10); + assert_eq!(word, 0); + } + + #[test] + fn test_store_padded_segment_should_add_n_elements_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let bytes = [1, 2, 3, 4, 5].span(); + memory.store_padded_segment(0, 5, bytes); + + // Then + let first_word = memory.load_internal(0); + assert( + first_word == 0x0102030405000000000000000000000000000000000000000000000000000000, + 'Wrong memory value' + ); + } + + #[test] + fn test_store_padded_segment_should_add_n_elements_padded_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // Memory initialization with a value to verify that if the size is out of the bound bytes, + // 0's have been copied. + // Otherwise, the memory value would be 0, and we wouldn't be able to check it. + memory.store(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0); + + // When + let bytes = [1, 2, 3, 4, 5].span(); + memory.store_padded_segment(0, 10, bytes); + + // Then + let first_word = memory.load_internal(0); + assert( + first_word == 0x01020304050000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 'Wrong memory value' + ); + } + + #[test] + fn test_store_padded_segment_should_add_n_elements_padded_with_offset_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // Memory initialization with a value to verify that if the size is out of the bound bytes, + // 0's have been copied. + // Otherwise, the memory value would be 0, and we wouldn't be able to check it. + memory.store(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0); + + // When + let bytes = [1, 2, 3, 4, 5].span(); + memory.store_padded_segment(5, 10, bytes); + + let first_word = memory.load_internal(0); + assert( + first_word == 0xFFFFFFFFFF01020304050000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 'Wrong memory value' + ); + } + + #[test] + fn test_store_padded_segment_should_add_n_elements_padded_with_offset_between_two_words_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // Memory initialization with a value to verify that if the size is out of the bound bytes, + // 0's have been copied. + // Otherwise, the memory value would be 0, and we wouldn't be able to check it. + memory.store(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 0); + memory.store(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 32); + + // When + let bytes = [1, 2, 3, 4, 5].span(); + memory.store_padded_segment(30, 10, bytes); + + // Then + let first_word = memory.load_internal(0); + assert( + first_word == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0102, + 'Wrong memory value' + ); + + let second_word = memory.load_internal(32); + assert( + second_word == 0x0304050000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 'Wrong memory value' + ); + } + + + #[test] + fn test_store_byte_should_store_byte_at_offset() { + // Given + let mut memory = MemoryTrait::new(); + + // When + memory.store_byte(0x01, 15); + + // Then + assert(memory.items[0] == 0x01, 'Wrong value for word 0'); + assert(memory.items[1] == 0x00, 'Wrong value for word 1'); + } + #[test] + fn test_store_byte_should_store_byte_at_offset_2() { + // Given + let mut memory = MemoryTrait::new(); + + // When + memory.store_byte(0xff, 14); + + // Then + assert(memory.items[0] == 0xff00, 'Wrong value for word 0'); + assert(memory.items[1] == 0x00, 'Wrong value for word 1'); + } + + #[test] + fn test_store_byte_should_store_byte_at_offset_in_existing_word() { + // Given + let mut memory = MemoryTrait::new(); + memory.items.insert(0, 0xFFFF); // Set the first word in memory to 0xFFFF; + memory.items.insert(1, 0xFFFF); + + // When + memory.store_byte(0x01, 30); + + // Then + assert(memory.items[0] == 0xFFFF, 'Wrong value for word 0'); + assert(memory.items[1] == 0x01FF, 'Wrong value for word 1'); + } + + #[test] + fn test_store_byte_should_store_byte_at_offset_in_new_word() { + // Given + let mut memory = MemoryTrait::new(); + + // When + memory.store_byte(0x01, 32); + + // Then + assert(memory.items[0] == 0x0, 'Wrong value for word 0'); + assert(memory.items[1] == 0x0, 'Wrong value for word 1'); + assert(memory.items[2] == 0x01000000000000000000000000000000, 'Wrong value for word 2'); + } + + #[test] + fn test_store_byte_should_store_byte_at_offset_in_new_word_with_existing_value_in_previous_word() { + // Given + let mut memory = MemoryTrait::new(); + memory.items.insert(0, 0x0100); + memory.items.insert(1, 0xffffffffffffffffffffffffffffffff); + + // When + memory.store_byte(0xAB, 17); + + // Then + assert(memory.items[0] == 0x0100, 'Wrong value in word 0'); + assert(memory.items[1] == 0xffABffffffffffffffffffffffffffff, 'Wrong value in word 1'); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/model.cairo b/cairo/kakarot-ssj/crates/evm/src/model.cairo new file mode 100644 index 000000000..fb217dfae --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/model.cairo @@ -0,0 +1,365 @@ +pub mod account; +pub mod vm; +pub use account::{Account, AccountTrait}; +pub use vm::{VM, VMTrait}; +use contracts::kakarot_core::{KakarotCore, IKakarotCore}; +use core::num::traits::{CheckedSub, Zero}; +use core::starknet::{EthAddress, ContractAddress}; +use crate::errors::EVMError; +use crate::precompiles::{ + FIRST_ROLLUP_PRECOMPILE_ADDRESS, FIRST_ETHEREUM_PRECOMPILE_ADDRESS, + LAST_ETHEREUM_PRECOMPILE_ADDRESS +}; +use crate::state::State; +use utils::fmt::{TSpanSetDebug}; +use utils::set::SpanSet; +use utils::traits::{EthAddressDefault, ContractAddressDefault, SpanDefault}; + +/// Represents the execution environment for EVM transactions. +#[derive(Destruct, Default)] +pub struct Environment { + /// The origin address of the transaction. + pub origin: Address, + /// The gas price for the transaction. + pub gas_price: u128, + /// The chain ID of the network. + pub chain_id: u64, + /// The previous RANDAO value. + pub prevrandao: u256, + /// The current block number. + pub block_number: u64, + /// The gas limit for the current block. + pub block_gas_limit: u64, + /// The timestamp of the current block. + pub block_timestamp: u64, + /// The address of the coinbase. + pub coinbase: EthAddress, + /// The base fee for the current block. + pub base_fee: u64, + /// The state of the EVM. + pub state: State +} + +/// Represents a message call in the EVM. +#[derive(Copy, Drop, Default, PartialEq, Debug)] +pub struct Message { + /// The address of the caller. + pub caller: Address, + /// The target address of the call. + pub target: Address, + /// The gas limit for the call. + pub gas_limit: u64, + /// The data passed to the call. + pub data: Span, + /// The code of the contract being called. + pub code: Span, + /// The address of the code being executed. + pub code_address: Address, + /// The value sent with the call. + pub value: u256, + /// Whether the value should be transferred. + pub should_transfer_value: bool, + /// The depth of the call stack. + pub depth: usize, + /// Whether the call is read-only. + pub read_only: bool, + /// Set of accessed addresses during execution. + pub accessed_addresses: SpanSet, + /// Set of accessed storage keys during execution. + pub accessed_storage_keys: SpanSet<(EthAddress, u256)>, +} + +/// Represents the result of an EVM execution. +#[derive(Drop, Debug)] +pub struct ExecutionResult { + /// The status of the execution result. + pub status: ExecutionResultStatus, + /// The return data of the execution. + pub return_data: Span, + /// The remaining gas after execution. + pub gas_left: u64, + /// Set of accessed addresses during execution. + pub accessed_addresses: SpanSet, + /// Set of accessed storage keys during execution. + pub accessed_storage_keys: SpanSet<(EthAddress, u256)>, + /// The amount of gas refunded during execution. + pub gas_refund: u64, +} + +/// Represents the status of an EVM execution result. +#[derive(Copy, Drop, PartialEq, Debug)] +pub enum ExecutionResultStatus { + /// The execution was successful. + Success, + /// The execution was reverted. + Revert, + /// An exception occurred during execution. + Exception, +} + +#[generate_trait] +pub impl ExecutionResultImpl of ExecutionResultTrait { + /// Creates an `ExecutionResult` for an exceptional failure. + /// + /// # Arguments + /// + /// * `error` - The error message as a span of bytes. + /// * `accessed_addresses` - Set of accessed addresses during execution. + /// * `accessed_storage_keys` - Set of accessed storage keys during execution. + /// + /// # Returns + /// + /// An `ExecutionResult` with the Exception status and provided data. + fn exceptional_failure( + error: Span, + accessed_addresses: SpanSet, + accessed_storage_keys: SpanSet<(EthAddress, u256)> + ) -> ExecutionResult { + ExecutionResult { + status: ExecutionResultStatus::Exception, + return_data: error, + gas_left: 0, + accessed_addresses, + accessed_storage_keys, + gas_refund: 0, + } + } + + /// Decrements the gas_left field of the current execution context by the value amount. + /// + /// # Arguments + /// + /// * `value` - The amount of gas to charge. + /// + /// # Returns + /// + /// `Ok(())` if successful, or `Err(EVMError::OutOfGas)` if there's not enough gas. + #[inline(always)] + fn charge_gas(ref self: ExecutionResult, value: u64) -> Result<(), EVMError> { + self.gas_left = self.gas_left.checked_sub(value).ok_or(EVMError::OutOfGas)?; + Result::Ok(()) + } + + /// Checks if the execution result status is Success. + /// + /// # Returns + /// + /// `true` if the status is Success, `false` otherwise. + fn is_success(self: @ExecutionResult) -> bool { + *self.status == ExecutionResultStatus::Success + } + + /// Checks if the execution result status is Exception. + /// + /// # Returns + /// + /// `true` if the status is Exception, `false` otherwise. + fn is_exception(self: @ExecutionResult) -> bool { + *self.status == ExecutionResultStatus::Exception + } + + /// Checks if the execution result status is Revert. + /// + /// # Returns + /// + /// `true` if the status is Revert, `false` otherwise. + fn is_revert(self: @ExecutionResult) -> bool { + *self.status == ExecutionResultStatus::Revert + } +} + +/// Represents a summary of an EVM execution. +#[derive(Destruct)] +pub struct ExecutionSummary { + /// The status of the execution result. + pub status: ExecutionResultStatus, + /// The return data of the execution. + pub return_data: Span, + /// The remaining gas after execution. + pub gas_left: u64, + /// The state of the EVM after execution. + pub state: State, + /// The amount of gas refunded during execution. + pub gas_refund: u64 +} + +/// Represents the result of an EVM transaction. +pub struct TransactionResult { + /// Whether the transaction was successful. + pub success: bool, + /// The return data of the transaction. + pub return_data: Span, + /// The amount of gas used by the transaction. + pub gas_used: u64, + /// The state of the EVM after the transaction. + pub state: State +} + +#[generate_trait] +pub impl TransactionResultImpl of TransactionResultTrait { + /// Creates a `TransactionResult` for an exceptional failure. + /// + /// # Arguments + /// + /// * `error` - The error message as a span of bytes. + /// * `gas_used` - The amount of gas used during the transaction. + /// + /// # Returns + /// + /// A `TransactionResult` with failure status and provided data. + fn exceptional_failure(error: Span, gas_used: u64) -> TransactionResult { + TransactionResult { + success: false, return_data: error, gas_used, state: Default::default() + } + } +} + +/// Represents an EVM event. +#[derive(Drop, Clone, Default, PartialEq)] +pub struct Event { + /// The keys of the event. + pub keys: Array, + /// The data of the event. + pub data: Array, +} + +/// Represents an address in both EVM and Starknet formats. +#[derive(Copy, Drop, PartialEq, Default, Debug)] +pub struct Address { + /// The EVM address. + pub evm: EthAddress, + /// The Starknet address. + pub starknet: ContractAddress, +} + +impl ZeroAddress of core::num::traits::Zero
{ + fn zero() -> Address { + Address { evm: Zero::zero(), starknet: Zero::zero(), } + } + fn is_zero(self: @Address) -> bool { + self.evm.is_zero() && self.starknet.is_zero() + } + fn is_non_zero(self: @Address) -> bool { + !self.is_zero() + } +} + +#[generate_trait] +pub impl AddressImpl of AddressTrait { + /// Checks if the EVM address is deployed. + /// + /// # Returns + /// + /// `true` if the address is deployed, `false` otherwise. + fn is_deployed(self: @EthAddress) -> bool { + let mut kakarot_state = KakarotCore::unsafe_new_contract_state(); + let address = kakarot_state.address_registry(*self); + return address.is_non_zero(); + } + + /// Checks if the address is a precompile for a call-family opcode. + /// + /// # Returns + /// + /// `true` if the address is a precompile, `false` otherwise. + fn is_precompile(self: EthAddress) -> bool { + let self: felt252 = self.into(); + return self != 0x00 + && (FIRST_ETHEREUM_PRECOMPILE_ADDRESS <= self.into() + && self.into() <= LAST_ETHEREUM_PRECOMPILE_ADDRESS) + || self.into() == FIRST_ROLLUP_PRECOMPILE_ADDRESS; + } +} + +/// Represents a native token transfer to be made when finalizing a transaction. +#[derive(Copy, Drop, PartialEq, Debug)] +pub struct Transfer { + /// The sender of the transfer. + pub sender: Address, + /// The recipient of the transfer. + pub recipient: Address, + /// The amount of tokens to transfer. + pub amount: u256 +} + +#[cfg(test)] +mod tests { + mod test_is_deployed { + use crate::model::AddressTrait; + use crate::test_utils; + use snforge_std::test_address; + use utils::helpers::compute_starknet_address; + + + #[test] + fn test_is_deployed_returns_true_if_in_registry() { + // Given + test_utils::setup_test_environment(); + let starknet_address = compute_starknet_address( + test_address(), test_utils::evm_address(), test_utils::uninitialized_account() + ); + test_utils::register_account(test_utils::evm_address(), starknet_address); + + // When + let is_deployed = test_utils::evm_address().is_deployed(); + + // Then + assert!(is_deployed); + } + + #[test] + fn test_is_deployed_undeployed() { + // Given + test_utils::setup_test_environment(); + + // When + let is_deployed = test_utils::evm_address().is_deployed(); + + // Then + assert!(!is_deployed); + } + } + mod test_is_precompile { + use core::starknet::EthAddress; + use crate::model::{AddressTrait}; + #[test] + fn test_is_precompile() { + // Given + let valid_precompiles = array![ + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0a, 0x100 + ]; + + //When + for el in valid_precompiles { + let evm_address: EthAddress = (el).try_into().unwrap(); + //Then + assert_eq!(true, evm_address.is_precompile()); + }; + } + + #[test] + fn test_is_precompile_zero() { + // Given + let evm_address: EthAddress = 0x0.try_into().unwrap(); + + // When + let is_precompile = evm_address.is_precompile(); + + // Then + assert_eq!(false, is_precompile); + } + + #[test] + fn test_is_not_precompile() { + // Given + let not_valid_precompiles = array![0xb, 0xc, 0xd, 0xe, 0xf, 0x99]; + + //When + for el in not_valid_precompiles { + let evm_address: EthAddress = (el).try_into().unwrap(); + //Then + assert_eq!(false, evm_address.is_precompile()); + }; + } + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/model/account.cairo b/cairo/kakarot-ssj/crates/evm/src/model/account.cairo new file mode 100644 index 000000000..849afc84e --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/model/account.cairo @@ -0,0 +1,488 @@ +use contracts::account_contract::{IAccountDispatcher, IAccountDispatcherTrait}; +use contracts::kakarot_core::{KakarotCore, IKakarotCore}; +use core::dict::{Felt252Dict, Felt252DictTrait}; +use core::num::traits::Zero; +use core::ops::SnapshotDeref; +use core::starknet::storage::{StoragePointerReadAccess, StoragePathEntry}; +use core::starknet::{ContractAddress, EthAddress, get_contract_address}; +use crate::backend::starknet_backend::fetch_balance; +use crate::model::Address; +use utils::constants::EMPTY_KECCAK; +use utils::helpers::compute_starknet_address; +use utils::traits::bytes::U8SpanExTrait; + +#[derive(Drop)] +struct AccountBuilder { + account: Account +} + +#[generate_trait] +impl AccountBuilderImpl of AccountBuilderTrait { + fn new(address: Address) -> AccountBuilder { + AccountBuilder { + account: Account { + address: address, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + balance: 0, + selfdestruct: false, + is_created: false + } + } + } + + #[inline(always)] + fn fetch_balance(mut self: AccountBuilder) -> AccountBuilder { + self.account.balance = fetch_balance(@self.account.address); + self + } + + #[inline(always)] + fn fetch_nonce(mut self: AccountBuilder) -> AccountBuilder { + let account = IAccountDispatcher { contract_address: self.account.address.starknet }; + self.account.nonce = account.get_nonce(); + self + } + + /// Loads the bytecode of a ContractAccount from Kakarot Core's contract storage into a + /// Span. + /// # Arguments + /// * `self` - The address of the Contract Account to load the bytecode from + /// # Returns + /// * The bytecode of the Contract Account as a ByteArray + #[inline(always)] + fn fetch_bytecode(mut self: AccountBuilder) -> AccountBuilder { + let account = IAccountDispatcher { contract_address: self.account.address.starknet }; + let bytecode = account.bytecode(); + self.account.code = bytecode; + self + } + + #[inline(always)] + fn fetch_code_hash(mut self: AccountBuilder) -> AccountBuilder { + let account = IAccountDispatcher { contract_address: self.account.address.starknet }; + self.account.code_hash = account.get_code_hash(); + self + } + + #[inline(always)] + fn build(self: AccountBuilder) -> Account { + self.account + } +} + +#[derive(Copy, Drop, PartialEq, Debug)] +pub struct Account { + pub address: Address, + pub code: Span, + pub code_hash: u256, + pub nonce: u64, + pub balance: u256, + pub selfdestruct: bool, + pub is_created: bool, +} + +#[generate_trait] +pub impl AccountImpl of AccountTrait { + fn get_starknet_address(evm_address: EthAddress) -> ContractAddress { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + let kakarot_storage = kakarot_state.snapshot_deref(); + let existing_address = kakarot_storage + .Kakarot_evm_to_starknet_address + .entry(evm_address) + .read(); + if existing_address.is_zero() { + return compute_starknet_address( + get_contract_address(), + evm_address, + kakarot_state.uninitialized_account_class_hash() + ); + } + existing_address + } + + + /// Fetches an account from Starknet + /// An non-deployed account is just an empty account. + /// # Arguments + /// * `address` - The address of the account to fetch` + /// + /// # Returns + /// The fetched account if it existed, otherwise a new empty account. + fn fetch_or_create(evm_address: EthAddress) -> Account { + let maybe_acc = Self::fetch(evm_address); + + match maybe_acc { + Option::Some(account) => account, + Option::None => { + let kakarot_state = KakarotCore::unsafe_new_contract_state(); + let starknet_address = kakarot_state.get_starknet_address(evm_address); + // If no account exists at `address`, then we are trying to + // access an undeployed account. We create an + // empty account with the correct address, fetch the balance, and return it. + AccountBuilderTrait::new(Address { starknet: starknet_address, evm: evm_address }) + .fetch_balance() + .build() + } + } + } + + /// Fetches an account from Starknet + /// + /// # Arguments + /// * `address` - The address of the account to fetch` + /// + /// # Returns + /// The fetched account if it existed, otherwise `None`. + fn fetch(evm_address: EthAddress) -> Option { + let mut kakarot_state = KakarotCore::unsafe_new_contract_state(); + let starknet_address = kakarot_state.address_registry(evm_address); + if starknet_address.is_zero() { + return Option::None; + } + let address = Address { starknet: starknet_address, evm: evm_address }; + Option::Some( + AccountBuilderTrait::new(address) + .fetch_nonce() + .fetch_bytecode() + .fetch_balance() + .fetch_code_hash() + .build() + ) + } + + + /// Returns whether an account exists at the given address by checking + /// whether it has code or a nonce. + /// + /// # Arguments + /// + /// * `account` - The instance of the account to check. + /// + /// # Returns + /// + /// `true` if an account exists at this address (has code or nonce), `false` otherwise. + #[inline(always)] + fn has_code_or_nonce(self: @Account) -> bool { + return !(*self.code).is_empty() || *self.nonce != 0; + } + + #[inline(always)] + fn is_created(self: @Account) -> bool { + *self.is_created + } + + #[inline(always)] + fn set_created(ref self: Account, is_created: bool) { + self.is_created = is_created; + } + + #[inline(always)] + fn balance(self: @Account) -> u256 { + *self.balance + } + + #[inline(always)] + fn set_balance(ref self: Account, value: u256) { + self.balance = value; + } + + #[inline(always)] + fn address(self: @Account) -> Address { + *self.address + } + + #[inline(always)] + fn evm_address(self: @Account) -> EthAddress { + *self.address.evm + } + + #[inline(always)] + fn starknet_address(self: @Account) -> ContractAddress { + *self.address.starknet + } + + /// Returns the bytecode of the EVM account (EOA or CA) + #[inline(always)] + fn bytecode(self: @Account) -> Span { + *self.code + } + + #[inline(always)] + fn code_hash(self: @Account) -> u256 { + *self.code_hash + } + + + /// Sets the nonce of the Account + /// # Arguments + /// * `self` The Account to set the nonce on + /// * `nonce` The new nonce + #[inline(always)] + fn set_nonce(ref self: Account, nonce: u64) { + self.nonce = nonce; + } + + #[inline(always)] + fn nonce(self: @Account) -> u64 { + *self.nonce + } + + /// Sets the code of the Account + /// Also sets the code hash to be synced with the code + /// # Arguments + /// * `self` The Account to set the code on + /// * `code` The new code + #[inline(always)] + fn set_code(ref self: Account, code: Span) { + self.code = code; + if code.is_empty() { + self.code_hash = EMPTY_KECCAK; + return; + } + let hash = code.compute_keccak256_hash(); + self.code_hash = hash; + } + + /// Registers an account for SELFDESTRUCT + /// This will cause the account to be erased at the end of the transaction + #[inline(always)] + fn selfdestruct(ref self: Account) { + self.selfdestruct = true; + } + + /// Returns whether the account is registered for SELFDESTRUCT + /// `true` means that the account will be erased at the end of the transaction + #[inline(always)] + fn is_selfdestruct(self: @Account) -> bool { + *self.selfdestruct + } + + /// Initializes a dictionary of valid jump destinations in EVM bytecode. + /// + /// This function iterates over the bytecode from the current index 'i'. + /// If the opcode at the current index is between 0x5f and 0x7f (PUSHN opcodes) (inclusive), + /// it skips the next 'n_args' opcodes, where 'n_args' is the opcode minus 0x5f. + /// If the opcode is 0x5b (JUMPDEST), it marks the current index as a valid jump destination. + /// It continues by jumping back to the body flag until it has processed the entire bytecode. + /// + /// # Arguments + /// * `bytecode` The bytecode to analyze + /// + /// # Returns + /// A dictionary of valid jump destinations in the bytecode + fn get_jumpdests(mut bytecode: Span) -> Felt252Dict { + let mut jumpdests: Felt252Dict = Default::default(); + let mut i: usize = 0; + while i < bytecode.len() { + let opcode = *bytecode[i]; + // checking for PUSH opcode family + if opcode >= 0x5f && opcode <= 0x7f { + let n_args = opcode.into() - 0x5f; + i += n_args + 1; + continue; + } + + if opcode == 0x5b { + jumpdests.insert(i.into(), true); + } + + i += 1; + }; + jumpdests + } +} + +#[cfg(test)] +mod tests { + mod test_has_code_or_nonce { + use crate::model::account::{Account, AccountTrait, Address}; + use utils::constants::EMPTY_KECCAK; + use utils::traits::bytes::U8SpanExTrait; + + #[test] + fn test_should_return_false_when_empty() { + let account = Account { + address: Address { evm: 1.try_into().unwrap(), starknet: 1.try_into().unwrap() }, + nonce: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + balance: 0, + selfdestruct: false, + is_created: false, + }; + + assert!(!account.has_code_or_nonce()); + } + + #[test] + fn test_should_return_true_when_code() { + let bytecode = [0x5b].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let account = Account { + address: Address { evm: 1.try_into().unwrap(), starknet: 1.try_into().unwrap() }, + nonce: 1, + code: bytecode, + code_hash: code_hash, + balance: 0, + selfdestruct: false, + is_created: false, + }; + + assert!(account.has_code_or_nonce()); + } + + #[test] + fn test_should_return_true_when_nonce() { + let account = Account { + address: Address { evm: 1.try_into().unwrap(), starknet: 1.try_into().unwrap() }, + nonce: 1, + code: [].span(), + code_hash: EMPTY_KECCAK, + balance: 0, + selfdestruct: false, + is_created: false, + }; + + assert!(account.has_code_or_nonce()); + } + } + + mod test_fetch { + use crate::model::account::{Account, AccountTrait, Address}; + use crate::test_utils::{ + register_account, setup_test_environment, uninitialized_account, evm_address, + native_token, + }; + use snforge_std::{test_address, start_mock_call}; + use snforge_utils::snforge_utils::assert_called; + use utils::constants::EMPTY_KECCAK; + use utils::helpers::compute_starknet_address; + + #[test] + fn test_should_fetch_data_from_storage_if_registered() { + // Given + setup_test_environment(); + let starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + register_account(evm_address(), starknet_address); + + let expected = Account { + address: Address { evm: evm_address(), starknet: starknet_address }, + nonce: 1, + code: [].span(), + code_hash: EMPTY_KECCAK, + balance: 100, + selfdestruct: false, + is_created: false, + }; + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 100); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + start_mock_call::>(starknet_address, selector!("bytecode"), [].span()); + start_mock_call::(starknet_address, selector!("get_code_hash"), EMPTY_KECCAK); + let account = AccountTrait::fetch(evm_address()).expect('Account should exist'); + + // Then + assert_eq!(account, expected); + assert_called(starknet_address, selector!("get_nonce")); + assert_called(starknet_address, selector!("bytecode")); + assert_called(starknet_address, selector!("get_code_hash")); + //TODO(starknet-foundry): we mocked the balanceOf call, but we should also check if it + //was called with the right data + assert_called(native_token(), selector!("balanceOf")); + } + + #[test] + fn test_should_return_none_if_not_registered() { + // Given + setup_test_environment(); + let _starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + + assert!(AccountTrait::fetch(evm_address()).is_none()); + } + } + + mod test_fetch_or_create { + use crate::model::account::{Account, AccountTrait, Address}; + use crate::test_utils::{ + register_account, setup_test_environment, uninitialized_account, evm_address, + native_token, + }; + use snforge_std::{test_address, start_mock_call}; + use snforge_utils::snforge_utils::assert_called; + use utils::constants::EMPTY_KECCAK; + use utils::helpers::compute_starknet_address; + + #[test] + fn test_should_fetch_data_from_storage_if_registered() { + // Given + setup_test_environment(); + let starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + register_account(evm_address(), starknet_address); + + let expected = Account { + address: Address { evm: evm_address(), starknet: starknet_address }, + nonce: 1, + code: [].span(), + code_hash: EMPTY_KECCAK, + balance: 100, + selfdestruct: false, + is_created: false, + }; + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 100); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + start_mock_call::>(starknet_address, selector!("bytecode"), [].span()); + start_mock_call::(starknet_address, selector!("get_code_hash"), EMPTY_KECCAK); + let account = AccountTrait::fetch_or_create(evm_address()); + + // Then + assert_eq!(account, expected); + assert_called(starknet_address, selector!("get_nonce")); + assert_called(starknet_address, selector!("bytecode")); + assert_called(starknet_address, selector!("get_code_hash")); + //TODO(starknet-foundry): we mocked the balanceOf call, but we should also check if it + //was called with the right data + assert_called(native_token(), selector!("balanceOf")); + } + + #[test] + fn test_should_create_new_account_with_starknet_balance_if_not_registered() { + // Given + setup_test_environment(); + let starknet_address = compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ); + + let expected = Account { + address: Address { evm: evm_address(), starknet: starknet_address }, + nonce: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + balance: 50, + selfdestruct: false, + is_created: false, + }; + + // When + start_mock_call::(native_token(), selector!("balanceOf"), 50); + let account = AccountTrait::fetch_or_create(evm_address()); + + // Then + assert_eq!(account, expected); + //TODO(starknet-foundry): we mocked the balanceOf call, but we should also check if it + //was called with the right data + assert_called(native_token(), selector!("balanceOf")); + } + } + //TODO(starknet-foundry): add a test for get_jumpdests +} diff --git a/cairo/kakarot-ssj/crates/evm/src/model/vm.cairo b/cairo/kakarot-ssj/crates/evm/src/model/vm.cairo new file mode 100644 index 000000000..d7704918b --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/model/vm.cairo @@ -0,0 +1,311 @@ +use core::cmp::min; +use core::dict::{Felt252Dict, Felt252DictTrait}; +use core::num::traits::{SaturatingSub, CheckedSub}; +use core::starknet::EthAddress; +use crate::errors::EVMError; +use crate::memory::Memory; +use crate::model::{Message, Environment, ExecutionResultStatus, ExecutionResult, AccountTrait}; +use crate::stack::Stack; +use utils::set::{Set, SetTrait, SpanSet, SpanSetTrait}; +use utils::traits::{SpanDefault}; + +#[derive(Default, Destruct)] +pub struct VM { + pub stack: Stack, + pub memory: Memory, + pub pc: usize, + pub valid_jumpdests: Felt252Dict, + pub return_data: Span, + pub env: Environment, + pub message: Message, + pub gas_left: u64, + pub running: bool, + pub error: bool, + pub accessed_addresses: Set, + pub accessed_storage_keys: Set<(EthAddress, u256)>, + pub gas_refund: u64 +} + + +#[generate_trait] +pub impl VMImpl of VMTrait { + #[inline(always)] + fn new(message: Message, env: Environment) -> VM { + VM { + stack: Default::default(), + memory: Default::default(), + pc: 0, + valid_jumpdests: AccountTrait::get_jumpdests(message.code), + return_data: [].span(), + env, + message, + gas_left: message.gas_limit, + running: true, + error: false, + accessed_addresses: message.accessed_addresses.clone_set(), + accessed_storage_keys: message.accessed_storage_keys.clone_set(), + gas_refund: 0 + } + } + + /// Decrements the gas_left field of the current vm by the value amount. + /// # Error : returns `EVMError::OutOfGas` if gas_left - value < 0 + #[inline(always)] + fn charge_gas(ref self: VM, value: u64) -> Result<(), EVMError> { + self.gas_left = match self.gas_left.checked_sub(value) { + Option::Some(gas_left) => gas_left, + Option::None => { return Result::Err(EVMError::OutOfGas); }, + }; + Result::Ok(()) + } + + + #[inline(always)] + fn pc(self: @VM) -> usize { + *self.pc + } + + #[inline(always)] + fn set_pc(ref self: VM, pc: usize) { + self.pc = pc; + } + + #[inline(always)] + fn is_valid_jump(ref self: VM, dest: u32) -> bool { + self.valid_jumpdests.get(dest.into()) + } + + #[inline(always)] + fn return_data(self: @VM) -> Span { + *self.return_data + } + + #[inline(always)] + fn set_return_data(ref self: VM, return_data: Span) { + self.return_data = return_data; + } + + #[inline(always)] + fn is_running(self: @VM) -> bool { + *self.running + } + + #[inline(always)] + fn stop(ref self: VM) { + self.running = false; + } + + #[inline(always)] + fn set_error(ref self: VM) { + self.error = true; + } + + #[inline(always)] + fn is_error(self: @VM) -> bool { + *self.error + } + + #[inline(always)] + fn message(self: @VM) -> Message { + *self.message + } + + #[inline(always)] + fn gas_left(self: @VM) -> u64 { + *self.gas_left + } + + #[inline(always)] + fn gas_refund(self: @VM) -> u64 { + *self.gas_refund + } + + #[inline(always)] + fn accessed_addresses(self: @VM) -> SpanSet { + self.accessed_addresses.spanset() + } + + #[inline(always)] + fn accessed_storage_keys(self: @VM) -> SpanSet<(EthAddress, u256)> { + self.accessed_storage_keys.spanset() + } + + /// Reads and returns data from bytecode starting at the provided pc. + /// + /// # Arguments + /// + /// * `self` - The `VM` instance to read the data from. + /// * `pc` - The starting position in the bytecode to read from. + /// * `len` - The length of the data to read from the bytecode. + /// + /// # Returns + /// + /// * A `Span` containing the requested bytecode slice. + /// * If the requested slice extends beyond the code length, returns remaining bytes. + #[inline(always)] + fn read_code(self: @VM, pc: usize, len: usize) -> Span { + let code_len = self.message().code.len(); + + // If pc is out of bounds, return an empty span + if pc >= code_len { + return [].span(); + } + + // Calculate the actual length to read + let remaining = code_len.saturating_sub(pc); + let actual_len = min(len, remaining); + + // Return the slice with the actual length + self.message().code.slice(pc, actual_len) + } + + #[inline(always)] + fn merge_child(ref self: VM, child: @ExecutionResult) { + match child.status { + ExecutionResultStatus::Success => { + self.accessed_addresses.extend(*child.accessed_addresses); + self.accessed_storage_keys.extend(*child.accessed_storage_keys); + self.gas_refund += *child.gas_refund; + self.gas_left += *child.gas_left; + self.return_data = *child.return_data; + }, + ExecutionResultStatus::Revert => { self.gas_left += *child.gas_left; }, + // If the call has halted exceptionnaly, the gas is not returned. + ExecutionResultStatus::Exception => {} + }; + } +} + +#[cfg(test)] +mod tests { + use crate::errors::EVMError; + use crate::model::Message; + use crate::model::vm::VMTrait; + use crate::test_utils::{tx_gas_limit, VMBuilderTrait}; + + #[test] + fn test_vm_default() { + let mut vm = VMTrait::new(Default::default(), Default::default()); + + assert!(vm.pc() == 0); + assert!(vm.is_running()); + assert!(!vm.error); + assert_eq!(vm.gas_left(), vm.message().gas_limit); + } + + + #[test] + fn test_set_pc() { + let mut vm = VMTrait::new(Default::default(), Default::default()); + + let new_pc = 42; + vm.set_pc(new_pc); + + assert(vm.pc() == new_pc, 'wrong pc'); + } + + #[test] + fn test_error() { + let mut vm = VMTrait::new(Default::default(), Default::default()); + + vm.set_error(); + + assert!(vm.error); + } + + #[test] + fn test_increment_gas_checked() { + let mut vm = VMTrait::new(Default::default(), Default::default()); + + assert_eq!(vm.gas_left(), vm.message().gas_limit); + + let result = vm.charge_gas(tx_gas_limit()); + + assert_eq!(result.unwrap_err(), EVMError::OutOfGas); + } + + #[test] + fn test_set_stopped() { + let mut vm = VMTrait::new(Default::default(), Default::default()); + + vm.stop(); + + assert!(!vm.is_running()) + } + + #[test] + fn test_read_code_within_bounds() { + let bytecode = [0x01, 0x02, 0x03, 0x04, 0x05].span(); + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + vm.set_pc(1); + + let read_code = vm.read_code(vm.pc(), 3); + + assert_eq!(read_code, [0x02, 0x03, 0x04].span()); + assert_eq!(vm.pc(), 1); + } + + #[test] + fn test_read_code_out_of_bounds() { + let bytecode = [0x01, 0x02, 0x03].span(); + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + vm.set_pc(2); + + let read_code = vm.read_code(vm.pc(), 3); + + assert_eq!(read_code, [0x03].span()); + assert_eq!(vm.pc(), 2); + } + + #[test] + fn test_read_code_at_boundary() { + let bytecode = [0x01, 0x02, 0x03, 0x04, 0x05].span(); + let mut vm = VMBuilderTrait::new_with_presets().with_bytecode(bytecode).build(); + vm.set_pc(3); + + let read_code = vm.read_code(vm.pc(), 3); + + assert_eq!(read_code, [0x04, 0x05].span()); + assert_eq!(vm.pc(), 3); + } + + #[test] + fn test_set_return() { + let mut vm = VMTrait::new(Default::default(), Default::default()); + vm.set_return_data([0x01, 0x02, 0x03].span()); + let return_data = vm.return_data(); + assert(return_data == [0x01, 0x02, 0x03].span(), 'wrong return data'); + } + + #[test] + fn test_return_data() { + let mut vm = VMTrait::new(Default::default(), Default::default()); + + let return_data = vm.return_data(); + assert(return_data.len() == 0, 'wrong length'); + } + + #[test] + fn test_is_valid_jump_destinations() { + // PUSH1, 0x03, JUMP, JUMPDEST, PUSH1, 0x09, JUMP, PUSH1 0x2, JUMPDDEST, PUSH1 0x2 + let mut message: Message = Default::default(); + message.code = [0x60, 0x3, 0x56, 0x5b, 0x60, 0x9, 0x56, 0x60, 0x2, 0x5b, 0x60, 0x2].span(); + + let mut vm = VMTrait::new(message, Default::default()); + + assert!(vm.is_valid_jump(0x3), "expected jump to be valid"); + assert!(vm.is_valid_jump(0x9), "expected jump to be valid"); + + assert!(!vm.is_valid_jump(0x4), "expected jump to be invalid"); + assert!(!vm.is_valid_jump(0x5), "expected jump to be invalid"); + } + + #[test] + fn test_valid_jump_destination_inside_jumpn() { + let mut message: Message = Default::default(); + message.code = [0x60, 0x5B, 0x60, 0x00].span(); + + let mut vm = VMTrait::new(message, Default::default()); + assert!(!vm.is_valid_jump(0x1), "expected false"); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles.cairo new file mode 100644 index 000000000..ff8dd9386 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles.cairo @@ -0,0 +1,155 @@ +mod blake2f; +mod ec_operations; +mod ec_recover; +mod identity; +mod modexp; +mod p256verify; +mod sha256; + +pub use blake2f::Blake2f; +pub use ec_operations::ec_add::EcAdd; +pub use ec_operations::ec_mul::EcMul; +pub use ec_recover::EcRecover; +pub use identity::Identity; +pub use modexp::ModExp; +pub use p256verify::P256Verify; +pub use sha256::Sha256; + +use core::starknet::EthAddress; +use core::traits::Into; +use crate::errors::EVMError; +use crate::model::vm::VM; +use crate::model::vm::VMTrait; +use utils::set::{Set, SetTrait}; + + +/// The starting address for Ethereum precompiles. +pub const FIRST_ETHEREUM_PRECOMPILE_ADDRESS: u256 = 0x01; + +/// The ending address for Ethereum precompiles (inclusive). +pub const LAST_ETHEREUM_PRECOMPILE_ADDRESS: u256 = 0x0a; + +/// The starting address for Rollup precompiles. +pub const FIRST_ROLLUP_PRECOMPILE_ADDRESS: u256 = 0x100; + +/// Returns a set of Ethereum precompile addresses. +/// +/// # Returns +/// +/// * `Set` - A set containing all Ethereum precompile addresses. +pub fn eth_precompile_addresses() -> Set { + let mut precompile_addresses: Array = array![]; + for i in FIRST_ETHEREUM_PRECOMPILE_ADDRESS + ..LAST_ETHEREUM_PRECOMPILE_ADDRESS + + 0x01 { + precompile_addresses.append(i.try_into().unwrap()); + }; + SetTrait::from_array(precompile_addresses) +} + + +/// Trait for implementing precompiles. +pub trait Precompile { + /// Returns the address of the precompile. + /// + /// # Returns + /// + /// * `EthAddress` - The address of the precompile. + fn address() -> EthAddress; + + /// Executes the precompile with the given input. + /// + /// # Arguments + /// + /// * `input` - A span of bytes representing the input data. + /// + /// # Returns + /// + /// * `Result<(u64, Span), EVMError>` - A tuple containing the gas used and the output data, + /// or an error if the execution failed. + fn exec(input: Span) -> Result<(u64, Span), EVMError>; +} + +#[generate_trait] +pub impl PrecompilesImpl of Precompiles { + /// Executes a precompile contract based on the current VM state. + /// + /// # Arguments + /// + /// * `ref vm` - A mutable reference to the VM instance. + /// + /// # Returns + /// + /// * `Result<(), EVMError>` - Ok if the precompile execution was successful, or an error if it + /// failed. + fn exec_precompile(ref vm: VM) -> Result<(), EVMError> { + let precompile_address = vm.message.code_address.evm; + let input = vm.message().data; + + let (gas, result) = if precompile_address.into() == 0x100 { + P256Verify::exec(input)? + } else { + match precompile_address.into() { + 0x00 => { + // we should never reach this branch! + panic!("pre-compile address can't be 0") + }, + 0x01 => { EcRecover::exec(input)? }, + 0x02 => { Sha256::exec(input)? }, + 0x03 => { + // we should never reach this branch! + panic!("pre-compile at address {:?} isn't implemented yet", precompile_address) + }, + 0x04 => { Identity::exec(input)? }, + 0x05 => { ModExp::exec(input)? }, + 0x06 => { EcAdd::exec(input)? }, + 0x07 => { EcMul::exec(input)? }, + 0x08 => { + // we should never reach this branch! + panic!("pre-compile at address {:?} isn't implemented yet", precompile_address) + }, + 0x09 => { Blake2f::exec(input)? }, + 0x0a => { + // Point Evaluation + panic!("pre-compile at address {:?} isn't implemented yet", precompile_address) + }, + _ => { + // we should never reach this branch! + panic!("address {:?} isn't a pre-compile", precompile_address) + } + } + }; + + vm.charge_gas(gas)?; + vm.return_data = result; + vm.stop(); + return Result::Ok(()); + } +} + + +#[cfg(test)] +mod tests { + use super::eth_precompile_addresses; + use utils::set::SetTrait; + + #[test] + fn test_eth_precompile_addresses() { + let addresses = eth_precompile_addresses(); + assert_eq!( + addresses.to_span(), + [ + 0x01.try_into().unwrap(), + 0x02.try_into().unwrap(), + 0x03.try_into().unwrap(), + 0x04.try_into().unwrap(), + 0x05.try_into().unwrap(), + 0x06.try_into().unwrap(), + 0x07.try_into().unwrap(), + 0x08.try_into().unwrap(), + 0x09.try_into().unwrap(), + 0x0a.try_into().unwrap() + ].span() + ); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/blake2f.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/blake2f.cairo new file mode 100644 index 000000000..ac16b81d2 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/blake2f.cairo @@ -0,0 +1,212 @@ +use core::array::ArrayTrait; +use core::option::OptionTrait; +use core::starknet::EthAddress; + +use crate::errors::{EVMError, ensure}; +use crate::precompiles::Precompile; +use utils::crypto::blake2_compress::compress; +use utils::traits::bytes::{FromBytes, ToBytes}; + +const GF_ROUND: u64 = 1; +const INPUT_LENGTH: usize = 213; + +pub impl Blake2f of Precompile { + #[inline(always)] + fn address() -> EthAddress { + 0x9.try_into().unwrap() + } + + fn exec(input: Span) -> Result<(u64, Span), EVMError> { + ensure( + input.len() == INPUT_LENGTH, EVMError::InvalidParameter('Blake2: wrong input length') + )?; + + let f = match (*input[212]).into() { + 0 => false, + 1 => true, + _ => { + return Result::Err(EVMError::InvalidParameter('Blake2: wrong final indicator')); + } + }; + + let rounds: u32 = input + .slice(0, 4) + .from_be_bytes() + .ok_or(EVMError::TypeConversionError('extraction of u32 failed'))?; + + let gas = (GF_ROUND * rounds.into()).into(); + + let mut h: Array = Default::default(); + let mut m: Array = Default::default(); + + let mut pos = 4; + for _ in 0..8_u8 { + // safe unwrap, because we have made sure of the input length to be 213 + h.append(input.slice(pos, 8).from_le_bytes().unwrap()); + pos += 8; + }; + + let mut pos = 68; + for _ in 0..16_u8 { + // safe unwrap, because we have made sure of the input length to be 213 + m.append(input.slice(pos, 8).from_le_bytes().unwrap()); + pos += 8 + }; + + let mut t: Array = Default::default(); + + // safe unwrap, because we have made sure of the input length to be 213 + t.append(input.slice(196, 8).from_le_bytes().unwrap()); + // safe unwrap, because we have made sure of the input length to be 213 + t.append(input.slice(204, 8).from_le_bytes().unwrap()); + + let res = compress(rounds, h.span(), m.span(), t.span(), f); + + let mut return_data: Array = Default::default(); + + for result in res { + let bytes = (*result).to_le_bytes_padded(); + return_data.append_span(bytes); + }; + + Result::Ok((gas, return_data.span())) + } +} + +#[cfg(test)] +mod tests { + use core::array::SpanTrait; + use crate::errors::EVMError; + use crate::instructions::MemoryOperationTrait; + use crate::instructions::SystemOperationsTrait; + use crate::memory::MemoryTrait; + use crate::precompiles::blake2f::Blake2f; + use crate::stack::StackTrait; + use crate::test_data::test_data_blake2f::{ + blake2_precompile_fail_wrong_length_input_1_test_case, + blake2_precompile_fail_wrong_length_input_2_test_case, + blake2_precompile_fail_wrong_length_input_3_test_case, blake2_precompile_pass_1_test_case, + blake2_precompile_pass_0_test_case, blake2_precompile_pass_2_test_case + }; + use crate::test_utils::{VMBuilderTrait, native_token, setup_test_environment}; + use snforge_std::start_mock_call; + use utils::traits::bytes::FromBytes; + + #[test] + fn test_blake2_precompile_fail_empty_input() { + let calldata = array![]; + + let res = Blake2f::exec(calldata.span()); + assert_eq!(res, Result::Err(EVMError::InvalidParameter('Blake2: wrong input length'))); + } + + #[test] + fn test_blake2_precompile_fail_wrong_length_input_1() { + let (calldata, _) = blake2_precompile_fail_wrong_length_input_1_test_case(); + + let res = Blake2f::exec(calldata); + assert_eq!(res, Result::Err(EVMError::InvalidParameter('Blake2: wrong input length'))); + } + #[test] + fn test_blake2_precompile_fail_wrong_length_input_2() { + let (calldata, _) = blake2_precompile_fail_wrong_length_input_2_test_case(); + + let res = Blake2f::exec(calldata); + assert_eq!(res, Result::Err(EVMError::InvalidParameter('Blake2: wrong input length'))); + } + + #[test] + fn test_blake2_precompile_fail_wrong_final_block_indicator_flag() { + let (calldata, _) = blake2_precompile_fail_wrong_length_input_3_test_case(); + + let res = Blake2f::exec(calldata); + assert_eq!(res, Result::Err(EVMError::InvalidParameter('Blake2: wrong final indicator'))); + } + + #[test] + fn test_blake2_precompile_pass_1() { + let (calldata, expected_result) = blake2_precompile_pass_1_test_case(); + let rounds: u32 = calldata.slice(0, 4).from_be_bytes().unwrap(); + + let (gas, result) = Blake2f::exec(calldata).unwrap(); + + assert_eq!(result, expected_result); + assert_eq!(gas, rounds.into()); + } + + #[test] + fn test_blake2_precompile_pass_0() { + let (calldata, expected_result) = blake2_precompile_pass_0_test_case(); + let rounds: u32 = calldata.slice(0, 4).from_be_bytes().unwrap(); + + let (gas, result) = Blake2f::exec(calldata).unwrap(); + + assert_eq!(result, expected_result); + assert_eq!(gas, rounds.into()); + } + + #[test] + fn test_blake2_precompile_pass_2() { + let (calldata, expected_result) = blake2_precompile_pass_2_test_case(); + let rounds: u32 = calldata.slice(0, 4).from_be_bytes().unwrap(); + + let (gas, result) = Blake2f::exec(calldata).unwrap(); + + assert_eq!(result, expected_result); + assert_eq!(gas, rounds.into()); + } + + // source: + // + #[test] + fn test_blake2_precompile_static_call() { + setup_test_environment(); + + let mut vm = VMBuilderTrait::new_with_presets().build(); + + // rounds + vm.stack.push(12).unwrap(); + vm.stack.push(3).unwrap(); + vm.exec_mstore8().unwrap(); + + // h + vm.stack.push(0x48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5).unwrap(); + vm.stack.push(4).unwrap(); + vm.exec_mstore().unwrap(); + vm.stack.push(0xd182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b).unwrap(); + vm.stack.push(36).unwrap(); + vm.exec_mstore().unwrap(); + + // m + vm.stack.push(0x6162630000000000000000000000000000000000000000000000000000000000).unwrap(); + vm.stack.push(68).unwrap(); + vm.exec_mstore().unwrap(); + + // t + vm.stack.push(3).unwrap(); + vm.stack.push(196).unwrap(); + vm.exec_mstore8().unwrap(); + + // f + vm.stack.push(1).unwrap(); + vm.stack.push(212).unwrap(); + vm.exec_mstore8().unwrap(); + + vm.stack.push(64).unwrap(); // retSize + vm.stack.push(213).unwrap(); // retOffset + vm.stack.push(213).unwrap(); // argsSize + vm.stack.push(0).unwrap(); // argsOffset + vm.stack.push(9).unwrap(); // address + vm.stack.push(0xFFFFFFFF).unwrap(); // gas + + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_staticcall().unwrap(); + + let mut result: Array = Default::default(); + vm.memory.load_n(64, ref result, 213); + + let (_, expected_result) = blake2_precompile_pass_1_test_case(); + + assert_eq!(result.span(), expected_result); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations.cairo new file mode 100644 index 000000000..d696d0fee --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations.cairo @@ -0,0 +1,107 @@ +pub(crate) mod ec_add; +pub(crate) mod ec_mul; +use core::circuit::CircuitElement as CE; +use core::circuit::CircuitInput as CI; +use core::circuit::{ + u96, u384, CircuitElement, CircuitInput, circuit_add, circuit_sub, circuit_mul, circuit_inverse, + EvalCircuitTrait, CircuitOutputsTrait, CircuitModulus, CircuitInputs +}; +use core::num::traits::Zero; +use garaga::core::circuit::AddInputResultTrait2; + +const BN254_ORDER: u256 = 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001; +const BN254_PRIME: u256 = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; +const BN254_PRIME_LIMBS: [ + u96 + ; 4] = [ + 0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0 +]; + +// Check if a point is on the curve. +// Point at infinity (0,0) will return false. +pub fn is_on_curve(x: u384, y: u384) -> bool { + let (b, _x, _y) = (CE::> {}, CE::> {}, CE::> {}); + + // Compute (y^2 - (x^3 + b)) % p_bn254 + let x2 = circuit_mul(_x, _x); + let x3 = circuit_mul(x2, _x); + let y2 = circuit_mul(_y, _y); + let rhs = circuit_add(x3, b); + let check = circuit_sub(y2, rhs); + + let modulus = TryInto::<_, CircuitModulus>::try_into(BN254_PRIME_LIMBS) + .unwrap(); // BN254 prime field modulus + + let mut circuit_inputs = (check,).new_inputs(); + // Prefill constants: + circuit_inputs = circuit_inputs.next_2([3, 0, 0, 0]); + // Fill inputs: + circuit_inputs = circuit_inputs.next_2(x); + circuit_inputs = circuit_inputs.next_2(y); + + let outputs = circuit_inputs.done_2().eval(modulus).unwrap(); + let zero_check: u384 = outputs.get_output(check); + return zero_check.is_zero(); +} + + +// Double BN254 EC point without checking if the point is on the curve +pub fn double_ec_point_unchecked(x: u384, y: u384) -> (u384, u384) { + // CONSTANT stack + let in0 = CE::> {}; // 0x3 + // INPUT stack + let (_x, _y) = (CE::> {}, CE::> {}); + + let x2 = circuit_mul(_x, _x); + let num = circuit_mul(in0, x2); + let den = circuit_add(_y, _y); + let inv_den = circuit_inverse(den); + let slope = circuit_mul(num, inv_den); + let slope_sqr = circuit_mul(slope, slope); + + let nx = circuit_sub(circuit_sub(slope_sqr, _x), _x); + let ny = circuit_sub(circuit_mul(slope, circuit_sub(_x, nx)), _y); + + let modulus = TryInto::<_, CircuitModulus>::try_into(BN254_PRIME_LIMBS) + .unwrap(); // BN254 prime field modulus + + let mut circuit_inputs = (nx, ny,).new_inputs(); + // Prefill constants: + circuit_inputs = circuit_inputs.next_2([0x3, 0x0, 0x0, 0x0]); // in0 + // Fill inputs: + circuit_inputs = circuit_inputs.next_2(x); // in1 + circuit_inputs = circuit_inputs.next_2(y); // in2 + + let outputs = circuit_inputs.done_2().eval(modulus).unwrap(); + + (outputs.get_output(nx), outputs.get_output(ny)) +} + + +// returns true if a == b mod p_bn254 +pub fn eq_mod_p(a: u384, b: u384) -> bool { + let in1 = CircuitElement::> {}; + let in2 = CircuitElement::> {}; + let sub = circuit_sub(in1, in2); + + let modulus = TryInto::<_, CircuitModulus>::try_into(BN254_PRIME_LIMBS) + .unwrap(); // BN254 prime field modulus + + let outputs = (sub,).new_inputs().next_2(a).next_2(b).done_2().eval(modulus).unwrap(); + + return outputs.get_output(sub).is_zero(); +} + +// returns true if a == -b mod p_bn254 +pub fn eq_neg_mod_p(a: u384, b: u384) -> bool { + let _a = CE::> {}; + let _b = CE::> {}; + let check = circuit_add(_a, _b); + + let modulus = TryInto::<_, CircuitModulus>::try_into(BN254_PRIME_LIMBS) + .unwrap(); // BN254 prime field modulus + + let outputs = (check,).new_inputs().next_2(a).next_2(b).done_2().eval(modulus).unwrap(); + + return outputs.get_output(check).is_zero(); +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations/ec_add.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations/ec_add.cairo new file mode 100644 index 000000000..1d8b9422e --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations/ec_add.cairo @@ -0,0 +1,185 @@ +use core::circuit::CircuitElement as CE; +use core::circuit::CircuitInput as CI; + +use core::circuit::{ + u384, circuit_sub, circuit_mul, circuit_inverse, EvalCircuitTrait, CircuitOutputsTrait, + CircuitModulus, CircuitInputs +}; +use core::option::Option; +use core::starknet::{EthAddress}; +use crate::errors::EVMError; +use crate::precompiles::Precompile; +use crate::precompiles::ec_operations::{ + eq_mod_p, eq_neg_mod_p, is_on_curve, double_ec_point_unchecked, BN254_PRIME_LIMBS, BN254_PRIME +}; +use garaga::core::circuit::AddInputResultTrait2; +use utils::traits::bytes::{ToBytes, U8SpanExTrait, FromBytes}; + + +const BASE_COST: u64 = 150; +const U256_BYTES_LEN: usize = 32; +pub impl EcAdd of Precompile { + #[inline(always)] + fn address() -> EthAddress { + 0x6.try_into().unwrap() + } + + fn exec(input: Span) -> Result<(u64, Span), EVMError> { + let gas = BASE_COST; + + // Pad the input to 128 bytes to avoid out-of-bounds accesses + let mut input = input.pad_right_with_zeroes(128); + + let x1: u256 = input.slice(0, 32).from_be_bytes().unwrap(); + + let y1: u256 = input.slice(32, 32).from_be_bytes().unwrap(); + + let x2: u256 = input.slice(64, 32).from_be_bytes().unwrap(); + + let y2: u256 = input.slice(96, 32).from_be_bytes().unwrap(); + + let (x, y) = match ec_add(x1, y1, x2, y2) { + Option::Some((x, y)) => { (x, y) }, + Option::None => { + return Result::Err(EVMError::InvalidParameter('invalid ec_add parameters')); + }, + }; + + let mut result_bytes = array![]; + // Append x to the result bytes. + let x_bytes = x.to_be_bytes_padded(); + result_bytes.append_span(x_bytes); + // Append y to the result bytes. + let y_bytes = y.to_be_bytes_padded(); + result_bytes.append_span(y_bytes); + + return Result::Ok((gas, result_bytes.span())); + } +} + + +fn ec_add(x1: u256, y1: u256, x2: u256, y2: u256) -> Option<(u256, u256)> { + if x1 >= BN254_PRIME || y1 >= BN254_PRIME || x2 >= BN254_PRIME || y2 >= BN254_PRIME { + return Option::None; + } + if x1 == 0 && y1 == 0 { + if x2 == 0 && y2 == 0 { + // Both are points at infinity, return either of them. + return Option::Some((x2, y2)); + } else { + // Only first point is at infinity. + let x2_u384: u384 = x2.into(); + let y2_u384: u384 = y2.into(); + if is_on_curve(x2_u384, y2_u384) { + // Second point is on the curve, return it. + return Option::Some((x2, y2)); + } else { + // Second point is not on the curve, return None (error). + return Option::None; + } + } + } else if x2 == 0 && y2 == 0 { + // Only second point is at infinity. + let x1_u384: u384 = x1.into(); + let y1_u384: u384 = y1.into(); + if is_on_curve(x1_u384, y1_u384) { + // First point is on the curve, return it. + return Option::Some((x1, y1)); + } else { + // First point is not on the curve, return None (error). + return Option::None; + } + } else { + // None of the points are at infinity. + let x1_u384: u384 = x1.into(); + let x2_u384: u384 = x2.into(); + let y1_u384: u384 = y1.into(); + let y2_u384: u384 = y2.into(); + + if is_on_curve(x1_u384, y1_u384) && is_on_curve(x2_u384, y2_u384) { + match ec_safe_add(x1_u384, y1_u384, x2_u384, y2_u384) { + Option::Some(( + x, y + )) => Option::Some( + ( + TryInto::::try_into(x).unwrap(), + TryInto::::try_into(y).unwrap() + ) + ), + Option::None => Option::Some((0, 0)), + } + } else { + // None of the points are infinity and at least one of them is not on the curve. + return Option::None; + } + } +} + + +// assumes that the points are on the curve and not the point at infinity. +// Returns None if the points are the same and opposite y coordinates (Point at infinity) +pub fn ec_safe_add(x1: u384, y1: u384, x2: u384, y2: u384) -> Option<(u384, u384)> { + let same_x = eq_mod_p(x1, x2); + + if same_x { + let opposite_y = eq_neg_mod_p(y1, y2); + + if opposite_y { + return Option::None; + } else { + let (x, y) = double_ec_point_unchecked(x1, y1); + return Option::Some((x, y)); + } + } else { + let (x, y) = add_ec_point_unchecked(x1, y1, x2, y2); + return Option::Some((x, y)); + } +} + +// Add two BN254 EC points without checking if: +// - the points are on the curve +// - the points are not the same +// - none of the points are the point at infinity +fn add_ec_point_unchecked(xP: u384, yP: u384, xQ: u384, yQ: u384) -> (u384, u384) { + // INPUT stack + let (_xP, _yP, _xQ, _yQ) = (CE::> {}, CE::> {}, CE::> {}, CE::> {}); + + let num = circuit_sub(_yP, _yQ); + let den = circuit_sub(_xP, _xQ); + let inv_den = circuit_inverse(den); + let slope = circuit_mul(num, inv_den); + let slope_sqr = circuit_mul(slope, slope); + + let nx = circuit_sub(circuit_sub(slope_sqr, _xP), _xQ); + let ny = circuit_sub(circuit_mul(slope, circuit_sub(_xP, nx)), _yP); + + let modulus = TryInto::<_, CircuitModulus>::try_into(BN254_PRIME_LIMBS) + .unwrap(); // BN254 prime field modulus + + let mut circuit_inputs = (nx, ny,).new_inputs(); + // Fill inputs: + circuit_inputs = circuit_inputs.next_2(xP); // in1 + circuit_inputs = circuit_inputs.next_2(yP); // in2 + circuit_inputs = circuit_inputs.next_2(xQ); // in3 + circuit_inputs = circuit_inputs.next_2(yQ); // in4 + + let outputs = circuit_inputs.done_2().eval(modulus).unwrap(); + + (outputs.get_output(nx), outputs.get_output(ny)) +} + + +#[cfg(test)] +mod tests { + use super::ec_add; + #[test] + fn test_ec_add() { + let x1 = 1; + let y1 = 2; + let x2 = 1; + let y2 = 2; + let (x, y) = ec_add(x1, y1, x2, y2).expect('ec_add failed'); + assert_eq!(x, 0x030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3); + assert_eq!(y, 0x15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations/ec_mul.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations/ec_mul.cairo new file mode 100644 index 000000000..bfb60a7e5 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_operations/ec_mul.cairo @@ -0,0 +1,142 @@ +use core::circuit::u384; +use core::option::Option; +use core::starknet::{EthAddress}; + +use crate::errors::EVMError; +use crate::precompiles::Precompile; +use crate::precompiles::ec_operations::ec_add::ec_safe_add; +use crate::precompiles::ec_operations::{is_on_curve, double_ec_point_unchecked, BN254_PRIME}; +use utils::traits::bytes::{ToBytes, U8SpanExTrait, FromBytes}; + +const BASE_COST: u64 = 6000; +const U256_BYTES_LEN: usize = 32; + +pub impl EcMul of Precompile { + fn address() -> EthAddress { + 0x7.try_into().unwrap() + } + + fn exec(mut input: Span) -> Result<(u64, Span), EVMError> { + let gas = BASE_COST; + + // Pad the input to 128 bytes to avoid out-of-bounds accesses + let mut input = input.pad_right_with_zeroes(96); + + let x1: u256 = input.slice(0, 32).from_be_bytes().unwrap(); + + let y1: u256 = input.slice(32, 32).from_be_bytes().unwrap(); + + let s: u256 = input.slice(64, 32).from_be_bytes().unwrap(); + + let (x, y) = match ec_mul(x1, y1, s) { + Option::Some((x, y)) => { (x, y) }, + Option::None => { + return Result::Err(EVMError::InvalidParameter('invalid ec_mul parameters')); + }, + }; + + // Append x and y to the result bytes. + let mut result_bytes = array![]; + let x_bytes = x.to_be_bytes_padded(); + result_bytes.append_span(x_bytes); + let y_bytes = y.to_be_bytes_padded(); + result_bytes.append_span(y_bytes); + + return Result::Ok((gas, result_bytes.span())); + } +} + +// Returns Option::None in case of error. +fn ec_mul(x1: u256, y1: u256, s: u256) -> Option<(u256, u256)> { + if x1 >= BN254_PRIME || y1 >= BN254_PRIME { + return Option::None; + } + if x1 == 0 && y1 == 0 { + // Input point is at infinity, return it + return Option::Some((x1, y1)); + } else { + // Point is not at infinity + let x1_u384: u384 = x1.into(); + let y1_u384: u384 = y1.into(); + + if is_on_curve(x1_u384, y1_u384) { + if s == 0 { + return Option::Some((0, 0)); + } else if s == 1 { + return Option::Some((x1, y1)); + } else { + // Point is on the curve. + // s is >= 2. + let bits = get_bits_little(s); + let pt = ec_mul_inner((x1_u384, y1_u384), bits); + match pt { + Option::Some(( + x, y + )) => Option::Some((x.try_into().unwrap(), y.try_into().unwrap())), + Option::None => Option::Some((0, 0)), + } + } + } else { + // Point is not on the curve + return Option::None; + } + } +} + +// Returns the bits of the 256 bit number in little endian format. +fn get_bits_little(s: u256) -> Array { + let mut bits = ArrayTrait::new(); + let mut s_low = s.low; + while s_low != 0 { + let (q, r) = core::traits::DivRem::div_rem(s_low, 2); + bits.append(r.into()); + s_low = q; + }; + let mut s_high = s.high; + if s_high != 0 { + while bits.len() != 128 { + bits.append(0); + } + } + while s_high != 0 { + let (q, r) = core::traits::DivRem::div_rem(s_high, 2); + bits.append(r.into()); + s_high = q; + }; + bits +} + + +// Should not be called outside of ec_mul. +// Returns Option::None in case of point at infinity. +// The size of bits array must be at minimum 2 and the point must be on the curve. +fn ec_mul_inner(pt: (u384, u384), mut bits: Array) -> Option<(u384, u384)> { + let (mut temp_x, mut temp_y) = pt; + let mut result: Option<(u384, u384)> = Option::None; + for bit in bits { + if bit != 0 { + match result { + Option::Some((xr, yr)) => result = ec_safe_add(temp_x, temp_y, xr, yr), + Option::None => result = Option::Some((temp_x, temp_y)), + }; + }; + let (_temp_x, _temp_y) = double_ec_point_unchecked(temp_x, temp_y); + temp_x = _temp_x; + temp_y = _temp_y; + }; + + return result; +} + +#[cfg(test)] +mod tests { + use super::ec_mul; + + #[test] + fn test_ec_mul() { + let (x1, y1, s) = (1, 2, 2); + let (x, y) = ec_mul(x1, y1, s).expect('ec_mul failed'); + assert_eq!(x, 0x030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3); + assert_eq!(y, 0x15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_recover.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_recover.cairo new file mode 100644 index 000000000..cfa2f68f9 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/ec_recover.cairo @@ -0,0 +1,150 @@ +use core::starknet::secp256_trait::{Signature, recover_public_key, Secp256PointTrait}; +use core::starknet::{ + EthAddress, eth_signature::{public_key_point_to_eth_address}, secp256k1::{Secp256k1Point}, + SyscallResultTrait +}; +use core::traits::Into; +use crate::errors::EVMError; +use crate::precompiles::Precompile; +use utils::traits::EthAddressIntoU256; +use utils::traits::bytes::{U8SpanExTrait, FromBytes, ToBytes}; +use utils::traits::{NumericIntoBool, BoolIntoNumeric}; + +const EC_RECOVER_PRECOMPILE_GAS_COST: u64 = 3000; + +pub impl EcRecover of Precompile { + #[inline(always)] + fn address() -> EthAddress { + 0x1.try_into().unwrap() + } + + fn exec(input: Span) -> Result<(u64, Span), EVMError> { + let gas = EC_RECOVER_PRECOMPILE_GAS_COST; + + // Pad the input to 128 bytes to avoid out-of-bounds accesses + let input = input.pad_right_with_zeroes(128); + let message_hash = input.slice(0, 32); + let message_hash = match message_hash.from_be_bytes() { + Option::Some(message_hash) => message_hash, + Option::None => { return Result::Ok((gas, [].span())); } + }; + + let v: Option = input.slice(32, 32).from_be_bytes(); + let y_parity = match v { + Option::Some(v) => { + if !(v == 27 || v == 28) { + return Result::Ok((gas, [].span())); + } + let y_parity = v - 27; //won't overflow because v is 27 or 28 + y_parity.into() + }, + Option::None => { return Result::Ok((gas, [].span())); } + }; + + let r: Option = input.slice(64, 32).from_be_bytes(); + let r = match r { + Option::Some(r) => r, + Option::None => { return Result::Ok((gas, [].span())); } + }; + + let s: Option = input.slice(96, 32).from_be_bytes(); + let s = match s { + Option::Some(s) => s, + Option::None => { return Result::Ok((gas, [].span())); } + }; + + let signature = Signature { r, s, y_parity }; + + let recovered_public_key = + match recover_public_key::(message_hash, signature) { + Option::Some(public_key_point) => { + // EVM spec requires that the public key is not the point at infinity + let (x, y) = public_key_point.get_coordinates().unwrap_syscall(); + if (x == 0 && y == 0) { + return Result::Ok((gas, [].span())); + } + public_key_point + }, + Option::None => { return Result::Ok((gas, [].span())); } + }; + + let eth_address: u256 = public_key_point_to_eth_address(recovered_public_key).into(); + let eth_address = eth_address.to_be_bytes_padded(); + + return Result::Ok((gas, eth_address)); + } +} + +#[cfg(test)] +mod tests { + use core::array::ArrayTrait; + use crate::instructions::SystemOperationsTrait; + use crate::memory::MemoryTrait; + + use crate::precompiles::ec_recover::EcRecover; + use crate::stack::StackTrait; + use crate::test_utils::setup_test_environment; + use crate::test_utils::{VMBuilderTrait, MemoryTestUtilsTrait, native_token}; + use snforge_std::start_mock_call; + use utils::traits::bytes::{ToBytes, FromBytes}; + + + // source: + // + #[test] + fn test_ec_recover_precompile() { + let msg_hash = 0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3_u256 + .to_be_bytes_padded(); + let v = 28_u256.to_be_bytes_padded(); + let r = 0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608_u256 + .to_be_bytes_padded(); + let s = 0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada_u256 + .to_be_bytes_padded(); + + let mut calldata = array![]; + calldata.append_span(msg_hash); + calldata.append_span(v); + calldata.append_span(r); + calldata.append_span(s); + + let (gas, result) = EcRecover::exec(calldata.span()).unwrap(); + + let result: u256 = result.from_be_bytes().unwrap(); + assert_eq!(result, 0x7156526fbd7a3c72969b54f64e42c10fbb768c8a); + assert_eq!(gas, 3000); + } + + // source: + // + #[test] + fn test_ec_precompile_static_call() { + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm + .memory + .store( + 0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3, 0x0 + ); // msg_hash + vm.memory.store_with_expansion(0x1C, 0x20); // v + vm + .memory + .store(0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608, 0x40); // r + vm + .memory + .store(0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada, 0x60); // s + + vm.stack.push(0x20).unwrap(); // retSize + vm.stack.push(0x80).unwrap(); // retOffset + vm.stack.push(0x80).unwrap(); // argsSize + vm.stack.push(0x0).unwrap(); // argsOffset + vm.stack.push(0x1).unwrap(); // address + vm.stack.push(0xFFFFFFFF).unwrap(); // gas + + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_staticcall().unwrap(); + + let result = vm.memory.load(0x80); + assert_eq!(result, 0x7156526fbd7a3c72969b54f64e42c10fbb768c8a); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/identity.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/identity.cairo new file mode 100644 index 000000000..e84ceaf80 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/identity.cairo @@ -0,0 +1,72 @@ +use core::starknet::EthAddress; +use crate::errors::EVMError; +use crate::precompiles::Precompile; +use utils::helpers::bytes_32_words_size; + +const BASE_COST: u64 = 15; +const COST_PER_WORD: u64 = 3; + +pub impl Identity of Precompile { + #[inline(always)] + fn address() -> EthAddress { + 0x4.try_into().unwrap() + } + + fn exec(input: Span) -> Result<(u64, Span), EVMError> { + let data_word_size = bytes_32_words_size(input.len()); + let gas = BASE_COST + data_word_size.into() * COST_PER_WORD; + + return Result::Ok((gas, input)); + } +} + +#[cfg(test)] +mod tests { + use core::result::ResultTrait; + use crate::instructions::SystemOperationsTrait; + + use crate::memory::MemoryTrait; + use crate::precompiles::identity::Identity; + use crate::stack::StackTrait; + use crate::test_utils::{ + VMBuilderTrait, MemoryTestUtilsTrait, native_token, setup_test_environment + }; + use snforge_std::start_mock_call; + + // source: + // + #[test] + fn test_identity_precompile() { + let calldata = [0x2A].span(); + + let (gas, result) = Identity::exec(calldata).unwrap(); + + assert_eq!(calldata, result); + assert_eq!(gas, 18); + } + + + // source: + // + //TODO(sn-foundry): fix or delete + #[test] + fn test_identity_precompile_static_call() { + setup_test_environment(); + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x20).unwrap(); // retSize + vm.stack.push(0x3F).unwrap(); // retOffset + vm.stack.push(0x20).unwrap(); // argsSize + vm.stack.push(0x1F).unwrap(); // argsOffset + vm.stack.push(0x4).unwrap(); // address + vm.stack.push(0xFFFFFFFF).unwrap(); // gas + + vm.memory.store_with_expansion(0x2A, 0x1F); + + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_staticcall().unwrap(); + + let result = vm.memory.load(0x3F); + assert_eq!(result, 0x2A); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/modexp.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/modexp.cairo new file mode 100644 index 000000000..d2b35b73e --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/modexp.cairo @@ -0,0 +1,302 @@ +use core::cmp::{min, max}; + +use core::num::traits::Bounded; +use core::num::traits::OverflowingAdd; +// CREDITS: The implementation has take reference from +// [revm](https://github.com/bluealloy/revm/blob/main/crates/precompile/src/modexp.rs) + +use core::option::OptionTrait; +use core::starknet::EthAddress; +use core::traits::TryInto; + +use crate::errors::EVMError; + +use crate::precompiles::Precompile; +use utils::crypto::modexp::lib::modexp; +use utils::traits::bytes::{U8SpanExTrait, FromBytes}; +use utils::traits::integer::BitsUsed; + +const HEADER_LENGTH: usize = 96; +const MIN_GAS: u64 = 200; + +pub impl ModExp of Precompile { + #[inline(always)] + fn address() -> EthAddress { + 0x5.try_into().unwrap() + } + + fn exec(input: Span) -> Result<(u64, Span), EVMError> { + // The format of input is: + // + // Where every length is a 32-byte left-padded integer representing the number of bytes + // to be taken up by the next value + + // safe unwraps, since we will always get a 32 byte span + let base_len: u256 = input.slice_right_padded(0, 32).from_be_bytes().unwrap(); + let exp_len: u256 = input.slice_right_padded(32, 32).from_be_bytes().unwrap(); + let mod_len: u256 = input.slice_right_padded(64, 32).from_be_bytes().unwrap(); + + // cast base_len, exp_len , modulus_len to usize, it does not make sense to handle larger + // values + let base_len: usize = match base_len.try_into() { + Option::Some(base_len) => { base_len }, + Option::None => { + return Result::Err(EVMError::InvalidParameter('base_len casting to u32 failed')); + } + }; + let exp_len: usize = match exp_len.try_into() { + Option::Some(exp_len) => { exp_len }, + Option::None => { + return Result::Err(EVMError::InvalidParameter('exp_len casting to u32 failed')); + } + }; + let mod_len: usize = match mod_len.try_into() { + Option::Some(mod_len) => { mod_len }, + Option::None => { + return Result::Err(EVMError::InvalidParameter('mod_len casting to u32 failed')); + } + }; + + // Handle a special case when both the base and mod length is zero + if base_len == 0 && mod_len == 0 { + return Result::Ok((MIN_GAS, [].span())); + } + + // Used to extract ADJUSTED_EXPONENT_LENGTH. + let exp_highp_len = min(exp_len, 32); + + let input = if input.len() >= HEADER_LENGTH { + input.slice(HEADER_LENGTH, input.len() - HEADER_LENGTH) + } else { + [].span() + }; + + let exp_highp = { + // get right padded bytes so if data.len is less then exp_len we will get right padded + // zeroes. + let right_padded_highp = input.slice_right_padded(base_len, 32); + // If exp_len is less then 32 bytes get only exp_len bytes and do left padding. + let out = right_padded_highp.slice(0, exp_highp_len).pad_left_with_zeroes(32); + match out.from_be_bytes() { + Option::Some(result) => result, + Option::None => { + return Result::Err(EVMError::InvalidParameter('failed to extract exp_highp')); + } + } + }; + + let gas = calc_gas(base_len.into(), exp_len.into(), mod_len.into(), exp_highp); + + // Padding is needed if the input does not contain all 3 values. + let base = input.slice_right_padded(0, base_len); + let exponent = input.slice_right_padded(base_len, exp_len); + + let (mod_start_idx, _) = base_len.overflowing_add(exp_len); + + let modulus = input.slice_right_padded(mod_start_idx, mod_len); + + let output = modexp(base, exponent, modulus); + + let return_data = output.pad_left_with_zeroes(mod_len); + Result::Ok((gas.into(), return_data)) + } +} + +// Calculate gas cost according to EIP 2565: +// https://eips.ethereum.org/EIPS/eip-2565 +fn calc_gas(base_length: u64, exp_length: u64, mod_length: u64, exp_highp: u256) -> u64 { + let multiplication_complexity = calculate_multiplication_complexity(base_length, mod_length); + + let iteration_count = calculate_iteration_count(exp_length, exp_highp); + + let gas = (multiplication_complexity * iteration_count.into()) / 3; + let gas: u64 = gas.try_into().unwrap_or(Bounded::::MAX); + + max(gas, 200) +} + +fn calculate_multiplication_complexity(base_length: u64, mod_length: u64) -> u256 { + let max_length = max(base_length, mod_length); + + let _8: NonZero = 8_u64.try_into().unwrap(); + let (words, rem) = DivRem::div_rem(max_length, _8); + + let words: u256 = if rem != 0 { + (words + 1).into() + } else { + words.into() + }; + + words * words +} + +fn calculate_iteration_count(exp_length: u64, exp_highp: u256) -> u64 { + let mut iteration_count: u64 = if exp_length < 33 { + if exp_highp == 0 { + 0 + } else { + (exp_highp.bits_used() - 1).into() + } + } else { + let length_part = 8 * (exp_length - 32); + let bits_part = if exp_highp != 0 { + exp_highp.bits_used() - 1 + } else { + 0 + }; + + length_part + bits_part.into() + }; + + max(iteration_count, 1) +} + +#[cfg(tests)] +mod tests { + use core::result::ResultTrait; + use core::starknet::EthAddress; + use core::starknet::testing::set_contract_address; + + use crate::instructions::SystemOperationsTrait; + + use crate::memory::MemoryTrait; + use crate::precompiles::Precompiles; + use crate::stack::StackTrait; + use crate::test_utils::{VMBuilderTrait, native_token, other_starknet_address}; + use evm_tests::test_precompiles::test_data::test_data_modexp::{ + test_modexp_modsize0_returndatasizeFiller_data, + test_modexp_create2callPrecompiles_test0_berlin_data, test_modexp_eip198_example_1_data, + test_modexp_eip198_example_2_data, test_modexp_nagydani_1_square_data, + test_modexp_nagydani_1_qube_data + }; + use snforge_std::{start_mock_call, test_address}; + use utils::helpers::U256Trait; + + // the tests are taken from + // [revm](https://github.com/bluealloy/revm/blob/0629883f5a40e913a5d9498fa37886348c858c70/crates/precompile/src/modexp.rs#L175) + + #[test] + fn test_modexp_modsize0_returndatasizeFiller_filler() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let (calldata, expected) = test_modexp_modsize0_returndatasizeFiller_data(); + + vm.message.target.evm = EthAddress { address: 5 }; + vm.message.data = calldata; + + let expected_gas = 44_954; + + let gas_before = vm.gas_left; + Precompiles::exec_precompile(ref vm).unwrap(); + let gas_after = vm.gas_left; + + assert_eq!(gas_before - gas_after, expected_gas); + assert_eq!(vm.return_data, expected); + } + + #[test] + fn test_modexp_create2callPrecompiles_test0_berlin() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let (calldata, expected) = test_modexp_create2callPrecompiles_test0_berlin_data(); + + vm.message.data = calldata; + vm.message.target.evm = EthAddress { address: 5 }; + let expected_gas = 1_360; + + let gas_before = vm.gas_left; + Precompiles::exec_precompile(ref vm).unwrap(); + let gas_after = vm.gas_left; + + assert_eq!(gas_before - gas_after, expected_gas); + assert_eq!(vm.return_data, expected); + } + + #[test] + fn test_modexp_eip198_example_1() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let (calldata, expected) = test_modexp_eip198_example_1_data(); + + vm.message.target.evm = EthAddress { address: 5 }; + vm.message.data = calldata; + let expected_gas = 1_360; + + let gas_before = vm.gas_left; + Precompiles::exec_precompile(ref vm).unwrap(); + let gas_after = vm.gas_left; + + assert_eq!(gas_before - gas_after, expected_gas); + assert_eq!(vm.return_data, expected); + } + + #[test] + fn test_modexp_eip198_example_2() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let (calldata, expected) = test_modexp_eip198_example_2_data(); + + vm.message.target.evm = EthAddress { address: 5 }; + vm.message.data = calldata; + let expected_gas = 1_360; + + let gas_before = vm.gas_left; + Precompiles::exec_precompile(ref vm).unwrap(); + let gas_after = vm.gas_left; + + assert_eq!(gas_before - gas_after, expected_gas); + assert_eq!(vm.return_data, expected); + } + + + #[test] + fn test_modexp_nagydani_1_square() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let (calldata, expected) = test_modexp_nagydani_1_square_data(); + + vm.message.target.evm = EthAddress { address: 5 }; + vm.message.data = calldata; + let expected_gas = 200; + + let gas_before = vm.gas_left; + Precompiles::exec_precompile(ref vm).unwrap(); + let gas_after = vm.gas_left; + + assert_eq!(gas_before - gas_after, expected_gas); + assert_eq!(vm.return_data, expected); + } + + #[test] + fn test_modexp_nagydani_1_qube() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let (calldata, expected) = test_modexp_nagydani_1_qube_data(); + + vm.message.target.evm = EthAddress { address: 5 }; + vm.message.data = calldata; + let expected_gas = 200; + + let gas_before = vm.gas_left; + Precompiles::exec_precompile(ref vm).unwrap(); + let gas_after = vm.gas_left; + + assert_eq!(gas_before - gas_after, expected_gas); + assert_eq!(vm.return_data, expected); + } + + #[test] + fn test_modexp_berlin_empty_input() { + let mut vm = VMBuilderTrait::new_with_presets().build(); + + let calldata = [].span(); + let expected = [].span(); + + vm.message.target.evm = EthAddress { address: 5 }; + vm.message.data = calldata; + + Precompiles::exec_precompile(ref vm).unwrap(); + + assert_eq!(vm.return_data, expected); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/p256verify.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/p256verify.cairo new file mode 100644 index 000000000..f0bbc4948 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/p256verify.cairo @@ -0,0 +1,227 @@ +use core::starknet::SyscallResultTrait; +use core::starknet::secp256_trait::{Secp256Trait}; +use core::starknet::{EthAddress, secp256r1::{Secp256r1Point}, secp256_trait::is_valid_signature}; +use crate::errors::{EVMError}; +use crate::precompiles::Precompile; +use utils::traits::bytes::FromBytes; + +const P256VERIFY_PRECOMPILE_GAS_COST: u64 = 3450; + +const ONE_32_BYTES: [ + u8 + ; 32] = [ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01 +]; + +pub impl P256Verify of Precompile { + #[inline(always)] + fn address() -> EthAddress { + 0x100.try_into().unwrap() + } + + fn exec(input: Span) -> Result<(u64, Span), EVMError> { + let gas = P256VERIFY_PRECOMPILE_GAS_COST; + + if input.len() != 160 { + return Result::Ok((gas, [].span())); + } + + let message_hash: u256 = input.slice(0, 32).from_be_bytes().unwrap(); + let r: u256 = input.slice(32, 32).from_be_bytes().unwrap(); + let s: u256 = input.slice(64, 32).from_be_bytes().unwrap(); + let x: u256 = input.slice(96, 32).from_be_bytes().unwrap(); + let y: u256 = input.slice(128, 32).from_be_bytes().unwrap(); + + let public_key: Option = Secp256Trait::secp256_ec_new_syscall(x, y) + .unwrap_syscall(); + let public_key = match public_key { + Option::Some(public_key) => public_key, + Option::None => { return Result::Ok((gas, [].span())); } + }; + + if !is_valid_signature(message_hash, r, s, public_key) { + return Result::Ok((gas, [].span())); + } + + return Result::Ok((gas, ONE_32_BYTES.span())); + } +} + +#[cfg(test)] +mod tests { + use core::array::ArrayTrait; + use crate::instructions::SystemOperationsTrait; + use crate::memory::MemoryTrait; + + use crate::precompiles::p256verify::P256Verify; + use crate::stack::StackTrait; + use crate::test_utils::{VMBuilderTrait}; + use crate::test_utils::{setup_test_environment, native_token}; + use snforge_std::start_mock_call; + use utils::traits::bytes::{ToBytes, FromBytes}; + + + // source: + // + #[test] + fn test_p256verify_precompile() { + let msg_hash = 0x4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d_u256 + .to_be_bytes_padded(); + let r = 0xa73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac_u256 + .to_be_bytes_padded(); + let s = 0x36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60_u256 + .to_be_bytes_padded(); + let x = 0x4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3_u256 + .to_be_bytes_padded(); + let y = 0x7618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e_u256 + .to_be_bytes_padded(); + + let mut calldata = array![]; + calldata.append_span(msg_hash); + calldata.append_span(r); + calldata.append_span(s); + calldata.append_span(x); + calldata.append_span(y); + + let (gas, result) = P256Verify::exec(calldata.span()).unwrap(); + + let result: u256 = result.from_be_bytes().expect('p256verify_precompile_test'); + assert_eq!(result, 0x01); + assert_eq!(gas, 3450); + } + + // source: + // + #[test] + //TODO(sn-foundry): fix or delete + fn test_p256verify_precompile_static_call() { + setup_test_environment(); + + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm + .memory + .store( + 0x4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d, 0x0 + ); // msg_hash + vm + .memory + .store(0xa73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac, 0x20); // r + vm + .memory + .store(0x36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60, 0x40); // s + vm + .memory + .store(0x4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3, 0x60); // x + vm + .memory + .store(0x7618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e, 0x80); // y + + vm.stack.push(0x20).unwrap(); // retSize + vm.stack.push(0xa0).unwrap(); // retOffset + vm.stack.push(0xa0).unwrap(); // argsSize + vm.stack.push(0x0).unwrap(); // argsOffset + vm.stack.push(0x100).unwrap(); // address + vm.stack.push(0xFFFFFFFF).unwrap(); // gas + + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_staticcall().unwrap(); + + let mut result = Default::default(); + vm.memory.load_n(0x20, ref result, 0xa0); + + assert_eq!(result.span(), super::ONE_32_BYTES.span()); + } + + #[test] + fn test_p256verify_precompile_input_too_short() { + let msg_hash = 0x4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d_u256 + .to_be_bytes_padded(); + let r = 0xa73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac_u256 + .to_be_bytes_padded(); + let s = 0x36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60_u256 + .to_be_bytes_padded(); + let x = 0x4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3_u256 + .to_be_bytes_padded(); + + let mut calldata = array![]; + calldata.append_span(msg_hash); + calldata.append_span(r); + calldata.append_span(s); + calldata.append_span(x); + + let (gas, result) = P256Verify::exec(calldata.span()).unwrap(); + + assert_eq!(result, [].span()); + assert_eq!(gas, 3450); + } + + //TODO(sn-foundry): fix or delete + #[test] + fn test_p256verify_precompile_input_too_short_static_call() { + setup_test_environment(); + + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm + .memory + .store( + 0x4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d, 0x0 + ); // msg_hash + vm + .memory + .store(0xa73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac, 0x20); // r + vm + .memory + .store(0x36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60, 0x40); // s + vm + .memory + .store(0x4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3, 0x60); // x + + vm.stack.push(0x01).unwrap(); // retSize + vm.stack.push(0x80).unwrap(); // retOffset + vm.stack.push(0x80).unwrap(); // argsSize + vm.stack.push(0x0).unwrap(); // argsOffset + vm.stack.push(0x100).unwrap(); // address + vm.stack.push(0xFFFFFFFF).unwrap(); // gas + + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_staticcall().unwrap(); + + let mut result = Default::default(); + vm.memory.load_n(0x1, ref result, 0x80); + + assert_eq!(result, array![0]); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/precompiles/sha256.cairo b/cairo/kakarot-ssj/crates/evm/src/precompiles/sha256.cairo new file mode 100644 index 000000000..60be11a69 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/precompiles/sha256.cairo @@ -0,0 +1,172 @@ +use core::sha256::compute_sha256_u32_array; +use core::starknet::EthAddress; +use crate::errors::EVMError; +use crate::precompiles::Precompile; +use utils::helpers::bytes_32_words_size; +use utils::math::Bitshift; +use utils::traits::bytes::{FromBytes, ToBytes}; + +const BASE_COST: u64 = 60; +const COST_PER_WORD: u64 = 12; + +pub impl Sha256 of Precompile { + #[inline(always)] + fn address() -> EthAddress { + 0x2.try_into().unwrap() + } + + fn exec(mut input: Span) -> Result<(u64, Span), EVMError> { + let data_word_size = bytes_32_words_size(input.len()); + let gas = BASE_COST + data_word_size.into() * COST_PER_WORD; + + let mut sha256_input: Array = array![]; + while let Option::Some(bytes4) = input.multi_pop_front::<4>() { + let bytes4 = (*bytes4).unbox(); + sha256_input.append(FromBytes::from_be_bytes(bytes4.span()).unwrap()); + }; + let (last_input_word, last_input_num_bytes) = if input.len() == 0 { + (0, 0) + } else { + let mut last_input_word: u32 = 0; + let mut last_input_num_bytes: u32 = 0; + for remaining_byte in input { + last_input_word = last_input_word.shl(8) + (*remaining_byte).into(); + last_input_num_bytes += 1; + }; + (last_input_word, last_input_num_bytes) + }; + let result_words_32: [u32; 8] = compute_sha256_u32_array( + sha256_input, last_input_word, last_input_num_bytes + ); + let mut result_bytes = array![]; + for word in result_words_32 + .span() { + let word_bytes = (*word).to_be_bytes_padded(); + result_bytes.append_span(word_bytes); + }; + + return Result::Ok((gas, result_bytes.span())); + } +} + +#[cfg(test)] +mod tests { + use core::result::ResultTrait; + use crate::instructions::SystemOperationsTrait; + + use crate::memory::MemoryTrait; + use crate::precompiles::sha256::Sha256; + use crate::stack::StackTrait; + use crate::test_utils::{ + VMBuilderTrait, MemoryTestUtilsTrait, native_token, setup_test_environment + }; + use snforge_std::{start_mock_call}; + use utils::traits::bytes::{ToBytes, FromBytes}; + + //source: + // + #[test] + fn test_sha_256_precompile() { + let calldata = array![0xFF,]; + + let (gas, result) = Sha256::exec(calldata.span()).unwrap(); + + let result: u256 = result.from_be_bytes().unwrap(); + let expected_result = 0xa8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89; + + assert_eq!(result, expected_result); + assert_eq!(gas, 72); + } + + #[test] + fn test_sha_256_precompile_full_word() { + let calldata = ToBytes::to_be_bytes( + 0xa8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89_u256 + ); + + let (gas, result) = Sha256::exec(calldata).unwrap(); + + let result: u256 = result.from_be_bytes().unwrap(); + let expected_result = 0xc0b057f584795eff8b06d5e420e71d747587d20de836f501921fd1b5741f1283; + + assert_eq!(result, expected_result); + assert_eq!(gas, 72); + } + + #[test] + fn test_sha256_more_than_32_bytes() { + let calldata = [ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xf3, + 0x45, + 0x78, + 0x90, + 0x7f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 + ]; + + let (gas, result) = Sha256::exec(calldata.span()).unwrap(); + + let result: u256 = result.from_be_bytes().unwrap(); + let expected_result = 0x3b745a1c00d035c334f358d007a430e4cf0ae63aa0556fb05529706de546464d; + + assert_eq!(result, expected_result); + assert_eq!(gas, 84); // BASE + 2 WORDS + } + + + // source: + // + #[test] + fn test_sha_256_precompile_static_call() { + setup_test_environment(); + + let mut vm = VMBuilderTrait::new_with_presets().build(); + + vm.stack.push(0x20).unwrap(); // retSize + vm.stack.push(0x20).unwrap(); // retOffset + vm.stack.push(0x1).unwrap(); // argsSize + vm.stack.push(0x1F).unwrap(); // argsOffset + vm.stack.push(0x2).unwrap(); // address + vm.stack.push(0xFFFFFFFF).unwrap(); // gas + + vm.memory.store_with_expansion(0xFF, 0x0); + + start_mock_call::(native_token(), selector!("balanceOf"), 0); + vm.exec_staticcall().unwrap(); + + let result = vm.memory.load(0x20); + let expected_result = 0xa8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89; + assert_eq!(result, expected_result); + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/stack.cairo b/cairo/kakarot-ssj/crates/evm/src/stack.cairo new file mode 100644 index 000000000..74ddcba68 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/stack.cairo @@ -0,0 +1,636 @@ +use core::dict::{Felt252Dict, Felt252DictTrait}; +//! Stack implementation. +//! # Example +//! ``` +//! use crate::stack::StackTrait; +//! +//! // Create a new stack instance. +//! let mut stack = StackTrait::new(); +//! let val_1: u256 = 1.into(); +//! let val_2: u256 = 1.into(); + +//! stack.push(val_1)?; +//! stack.push(val_2)?; + +//! let value = stack.pop()?; +//! ``` +use core::nullable::{NullableTrait}; +use core::num::traits::Bounded; +use core::starknet::EthAddress; +use crate::errors::{ensure, EVMError}; + +use utils::constants; +use utils::i256::i256; +use utils::traits::{TryIntoResult}; + + +//TODO(optimization): make len `felt252` based to avoid un-necessary checks +#[derive(Destruct, Default)] +pub struct Stack { + pub items: Felt252Dict>, + pub len: usize, +} + +pub trait StackTrait { + fn new() -> Stack; + fn push(ref self: Stack, item: u256) -> Result<(), EVMError>; + fn pop(ref self: Stack) -> Result; + fn pop_usize(ref self: Stack) -> Result; + fn pop_saturating_usize(ref self: Stack) -> Result; + fn pop_u64(ref self: Stack) -> Result; + fn pop_saturating_u64(ref self: Stack) -> Result; + fn pop_u128(ref self: Stack) -> Result; + fn pop_saturating_u128(ref self: Stack) -> Result; + fn pop_i256(ref self: Stack) -> Result; + fn pop_eth_address(ref self: Stack) -> Result; + fn pop_n(ref self: Stack, n: usize) -> Result, EVMError>; + fn peek(ref self: Stack) -> Option; + fn peek_at(ref self: Stack, index: usize) -> Result; + fn swap_i(ref self: Stack, index: usize) -> Result<(), EVMError>; + fn len(self: @Stack) -> usize; + fn is_empty(self: @Stack) -> bool; +} + +impl StackImpl of StackTrait { + #[inline(always)] + fn new() -> Stack { + Default::default() + } + + /// Pushes a new bytes32 word onto the stack. + /// + /// The item is stored at the current length of the stack. + /// + /// # Errors + /// + /// If the stack is full, returns with a StackOverflow error. + #[inline(always)] + fn push(ref self: Stack, item: u256) -> Result<(), EVMError> { + let length = self.len(); + // we can store at most 1024 256-bits words + ensure(length != constants::STACK_MAX_DEPTH, EVMError::StackOverflow)?; + + self.items.insert(length.into(), NullableTrait::new(item)); + self.len += 1; + Result::Ok(()) + } + + /// Pops the top item off the stack. + /// + /// # Errors + /// + /// If the stack is empty, returns with a StackUnderflow error. + #[inline(always)] + fn pop(ref self: Stack) -> Result { + ensure(self.len() != 0, EVMError::StackUnderflow)?; + + self.len -= 1; + let item = self.items.get(self.len().into()); + Result::Ok(item.deref()) + } + + /// Calls `Stack::pop` and tries to convert it to usize + /// + /// # Errors + /// + /// Returns `EVMError::StackError` with appropriate message + /// In case: + /// - Stack is empty + /// - Type conversion failed + #[inline(always)] + fn pop_usize(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + let item: usize = item.try_into_result()?; + Result::Ok(item) + } + + /// Calls `Stack::pop` and saturates the result to usize + #[inline(always)] + fn pop_saturating_usize(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + if item.high != 0 { + return Result::Ok(Bounded::::MAX); + }; + match item.low.try_into() { + Option::Some(value) => Result::Ok(value), + Option::None => Result::Ok(Bounded::::MAX), + } + } + + /// Calls `Stack::pop` and tries to convert it to u64 + /// + /// # Errors + /// + /// Returns `EVMError::StackError` with appropriate message + /// In case: + /// - Stack is empty + /// - Type conversion failed + #[inline(always)] + fn pop_u64(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + let item: u64 = item.try_into_result()?; + Result::Ok(item) + } + + /// Calls `Stack::pop` and saturates the result to u64 + #[inline(always)] + fn pop_saturating_u64(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + if item.high != 0 { + return Result::Ok(Bounded::::MAX); + }; + match item.low.try_into() { + Option::None => Result::Ok(Bounded::::MAX), + Option::Some(value) => Result::Ok(value), + } + } + + /// Calls `Stack::pop` and convert it to i256 + /// + /// # Errors + /// + /// Returns `EVMError::StackError` with appropriate message + /// In case: + /// - Stack is empty + #[inline(always)] + fn pop_i256(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + let item: i256 = item.into(); + Result::Ok(item) + } + + + /// Calls `Stack::pop` and tries to convert it to u128 + /// + /// # Errors + /// + /// Returns `EVMError::StackError` with appropriate message + /// In case: + /// - Stack is empty + /// - Type conversion failed + #[inline(always)] + fn pop_u128(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + let item: u128 = item.try_into_result()?; + Result::Ok(item) + } + + /// Calls `Stack::pop` and saturates the result to u128 + /// + #[inline(always)] + fn pop_saturating_u128(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + if item.high != 0 { + return Result::Ok(Bounded::::MAX); + }; + Result::Ok(item.low) + } + + /// Calls `Stack::pop` and converts it to an EthAddress + /// If the value is bigger than an EthAddress, it will be truncated to keep the lower 160 bits. + /// + /// # Errors + /// + /// Returns `EVMError::StackError` with appropriate message + /// In case: + /// - Stack is empty + #[inline(always)] + fn pop_eth_address(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + let item: EthAddress = item.into(); + Result::Ok(item) + } + + /// Pops N elements from the stack. + /// + /// # Errors + /// + /// If the stack length is less than than N, returns with a StackUnderflow error. + fn pop_n(ref self: Stack, mut n: usize) -> Result, EVMError> { + ensure(!(n > self.len()), EVMError::StackUnderflow)?; + let mut popped_items = ArrayTrait::::new(); + for _ in 0..n { + popped_items.append(self.pop().unwrap()); + }; + Result::Ok(popped_items) + } + + /// Peeks at the top item on the stack. + /// + /// # Errors + /// + /// If the stack is empty, returns None. + #[inline(always)] + fn peek(ref self: Stack) -> Option { + if self.len() == 0 { + Option::None(()) + } else { + let last_index = self.len() - 1; + let item = self.items.get(last_index.into()); + Option::Some(item.deref()) + } + } + + /// Peeks at the item at the given index on the stack. + /// index is 0-based, where 0 is the top of the stack (most recently pushed item). + /// + /// # Errors + /// + /// If the index is greater than the stack length, returns with a StackUnderflow error. + #[inline(always)] + fn peek_at(ref self: Stack, index: usize) -> Result { + ensure(index < self.len(), EVMError::StackUnderflow)?; + + let position = self.len() - 1 - index; + let item = self.items.get(position.into()); + + Result::Ok(item.deref()) + } + + /// Swaps the item at the given index with the item on top of the stack. + /// index is 0-based, where 0 would mean no swap (top item swapped with itself). + #[inline(always)] + fn swap_i(ref self: Stack, index: usize) -> Result<(), EVMError> { + ensure(index < self.len(), EVMError::StackUnderflow)?; + + let position_0: felt252 = self.len().into() - 1; + let position_item: felt252 = position_0 - index.into(); + let top_item = self.items.get(position_0); + let swapped_item = self.items.get(position_item); + self.items.insert(position_0, swapped_item.into()); + self.items.insert(position_item, top_item.into()); + Result::Ok(()) + } + + /// Returns the length of the stack. + #[inline(always)] + fn len(self: @Stack) -> usize { + *self.len + } + + /// Returns true if the stack is empty. + #[inline(always)] + fn is_empty(self: @Stack) -> bool { + self.len() == 0 + } +} + +#[cfg(test)] +mod tests { + // Core lib imports + + // Internal imports + use crate::stack::StackTrait; + use utils::constants; + + #[test] + fn test_stack_new_should_return_empty_stack() { + // When + let mut stack = StackTrait::new(); + + // Then + assert_eq!(stack.len(), 0); + } + + #[test] + fn test_empty_should_return_if_stack_is_empty() { + // Given + let mut stack = StackTrait::new(); + + // Then + assert!(stack.is_empty()); + + // When + stack.push(1).unwrap(); + // Then + assert!(!stack.is_empty()); + } + + #[test] + fn test_len_should_return_the_length_of_the_stack() { + // Given + let mut stack = StackTrait::new(); + + // Then + assert_eq!(stack.len(), 0); + + // When + stack.push(1).unwrap(); + // Then + assert_eq!(stack.len(), 1); + } + + mod push { + use crate::errors::{EVMError}; + use super::StackTrait; + + use super::constants; + + #[test] + fn test_should_add_an_element_to_the_stack() { + // Given + let mut stack = StackTrait::new(); + + // When + stack.push(1).unwrap(); + + // Then + let res = stack.peek().unwrap(); + + assert_eq!(stack.is_empty(), false); + assert_eq!(stack.len(), 1); + assert_eq!(res, 1); + } + + #[test] + fn test_should_fail_when_overflow() { + // Given + let mut stack = StackTrait::new(); + + // When + for _ in 0..constants::STACK_MAX_DEPTH { + stack.push(1).unwrap(); + }; + + // Then + let res = stack.push(1); + assert_eq!(stack.len(), constants::STACK_MAX_DEPTH); + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), EVMError::StackOverflow); + } + } + + mod pop { + use core::num::traits::Bounded; + use crate::errors::EVMError; + use super::StackTrait; + use utils::traits::StorageBaseAddressPartialEq; + + #[test] + fn test_should_pop_an_element_from_the_stack() { + // Given + let mut stack = StackTrait::new(); + stack.push(1).unwrap(); + stack.push(2).unwrap(); + stack.push(3).unwrap(); + + // When + let last_item = stack.pop().unwrap(); + + // Then + assert_eq!(last_item, 3); + assert_eq!(stack.len(), 2); + } + + + #[test] + fn test_should_pop_N_elements_from_the_stack() { + // Given + let mut stack = StackTrait::new(); + stack.push(1).unwrap(); + stack.push(2).unwrap(); + stack.push(3).unwrap(); + + // When + let elements = stack.pop_n(3).unwrap(); + + // Then + assert_eq!(stack.len(), 0); + assert_eq!(elements.len(), 3); + assert_eq!(elements.span(), [3, 2, 1].span()) + } + + + #[test] + fn test_pop_return_err_when_stack_underflow() { + // Given + let mut stack = StackTrait::new(); + + // When & Then + let result = stack.pop(); + assert(result.is_err(), 'should return Err '); + assert!( + result.unwrap_err() == EVMError::StackUnderflow, "should return StackUnderflow" + ); + } + + #[test] + fn test_pop_n_should_return_err_when_stack_underflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(1).unwrap(); + + // When & Then + let result = stack.pop_n(2); + assert(result.is_err(), 'should return Error'); + assert!( + result.unwrap_err() == EVMError::StackUnderflow, "should return StackUnderflow" + ); + } + + #[test] + fn test_pop_saturating_usize_should_return_max_when_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(Bounded::::MAX.into()).unwrap(); + + // When + let result = stack.pop_saturating_usize(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Bounded::::MAX); + } + + #[test] + fn test_pop_saturating_usize_should_return_value_when_no_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(1234567890).unwrap(); + + // When + let result = stack.pop_saturating_usize(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1234567890); + } + + + #[test] + fn test_pop_saturating_u64_should_return_max_when_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(Bounded::::MAX).unwrap(); + + // When + let result = stack.pop_saturating_u64(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Bounded::::MAX); + } + + #[test] + fn test_pop_saturating_u64_should_return_value_when_no_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(1234567890).unwrap(); + + // When + let result = stack.pop_saturating_u64(); + + // Then + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1234567890); + } + + + #[test] + fn test_pop_saturating_u128_should_return_max_when_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(Bounded::::MAX).unwrap(); + + // When + let result = stack.pop_saturating_u128(); + + // Then + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Bounded::::MAX); + } + + #[test] + fn test_pop_saturating_u128_should_return_value_when_no_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(1234567890).unwrap(); + + // When + let result = stack.pop_saturating_u128(); + + // Then + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1234567890); + } + } + + mod peek { + use crate::errors::{EVMError}; + use super::StackTrait; + + #[test] + fn test_should_return_last_item() { + // Given + let mut stack = StackTrait::new(); + + // When + stack.push(1).unwrap(); + stack.push(2).unwrap(); + + // Then + let last_item = stack.peek().unwrap(); + assert_eq!(last_item, 2); + } + + + #[test] + fn test_should_return_stack_at_given_index_when_value_is_0() { + // Given + let mut stack = StackTrait::new(); + stack.push(1).unwrap(); + stack.push(2).unwrap(); + stack.push(3).unwrap(); + + // When + let result = stack.peek_at(0).unwrap(); + + // Then + assert_eq!(result, 3); + } + + #[test] + fn test_should_return_stack_at_given_index_when_value_is_1() { + // Given + let mut stack = StackTrait::new(); + stack.push(1).unwrap(); + stack.push(2).unwrap(); + stack.push(3).unwrap(); + + // When + let result = stack.peek_at(1).unwrap(); + + // Then + assert_eq!(result, 2); + } + + #[test] + fn test_should_return_err_when_underflow() { + // Given + let mut stack = StackTrait::new(); + + // When & Then + let result = stack.peek_at(1); + + assert(result.is_err(), 'should return an EVMError'); + assert!( + result.unwrap_err() == EVMError::StackUnderflow, "should return StackUnderflow" + ); + } + } + + mod swap { + use crate::errors::{EVMError}; + use super::StackTrait; + + #[test] + fn test_should_swap_2_stack_items() { + // Given + let mut stack = StackTrait::new(); + stack.push(1).unwrap(); + stack.push(2).unwrap(); + stack.push(3).unwrap(); + stack.push(4).unwrap(); + let index3 = stack.peek_at(3).unwrap(); + assert_eq!(index3, 1); + let index2 = stack.peek_at(2).unwrap(); + assert_eq!(index2, 2); + let index1 = stack.peek_at(1).unwrap(); + assert_eq!(index1, 3); + let index0 = stack.peek_at(0).unwrap(); + assert_eq!(index0, 4); + + // When + stack.swap_i(2).expect('swap failed'); + + // Then + let index3 = stack.peek_at(3).unwrap(); + assert_eq!(index3, 1); + let index2 = stack.peek_at(2).unwrap(); + assert_eq!(index2, 4); + let index1 = stack.peek_at(1).unwrap(); + assert_eq!(index1, 3); + let index0 = stack.peek_at(0).unwrap(); + assert_eq!(index0, 2); + } + + #[test] + fn test_should_return_err_when_index_1_is_underflow() { + // Given + let mut stack = StackTrait::new(); + + // When & Then + let result = stack.swap_i(1); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::StackUnderflow); + } + + #[test] + fn test_should_return_err_when_index_2_is_underflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(1).unwrap(); + + // When & Then + let result = stack.swap_i(2); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::StackUnderflow); + } + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/state.cairo b/cairo/kakarot-ssj/crates/evm/src/state.cairo new file mode 100644 index 000000000..0ea8ce3ca --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/state.cairo @@ -0,0 +1,784 @@ +use core::dict::{Felt252Dict, Felt252DictTrait}; +use core::hash::{HashStateTrait, HashStateExTrait}; +use core::nullable::{match_nullable, FromNullableResult}; +use core::num::traits::{OverflowingAdd, OverflowingSub}; +use core::poseidon::PoseidonTrait; +use core::starknet::storage_access::{StorageBaseAddress, storage_base_address_from_felt252}; +use core::starknet::{EthAddress}; +use crate::backend::starknet_backend::fetch_original_storage; + +use crate::errors::{ensure, EVMError, BALANCE_OVERFLOW}; +use crate::model::account::{AccountTrait}; +use crate::model::{Event, Transfer, Account}; +use utils::set::{Set, SetTrait}; + +/// The `StateChangeLog` tracks the changes applied to storage during the execution of a +/// transaction. +/// Upon exiting an execution context, contextual changes must be finalized into transactional +/// changes. +/// Upon exiting the transaction, transactional changes must be finalized into storage updates. +/// +/// # Type Parameters +/// +/// * `T` - The type of values stored in the log. +/// +/// # Fields +/// +/// * `changes` - A `Felt252Dict` of contextual changes. Tracks the changes applied inside a single +/// execution context. +/// * `keyset` - An `Array` of contextual keys. +pub struct StateChangeLog { + pub changes: Felt252Dict>, + pub keyset: Set, +} + +impl StateChangeLogDestruct> of Destruct> { + fn destruct(self: StateChangeLog) nopanic { + self.changes.squash(); + } +} + +impl StateChangeLogDefault> of Default> { + fn default() -> StateChangeLog { + StateChangeLog { changes: Default::default(), keyset: Default::default(), } + } +} + +#[generate_trait] +impl StateChangeLogImpl, +Copy> of StateChangeLogTrait { + /// Reads a value from the StateChangeLog. Starts by looking for the value in the + /// contextual changes. If the value is not found, looks for it in the + /// transactional changes. + /// + /// # Arguments + /// + /// * `self` - A reference to a `StateChangeLog` instance. + /// * `key` - The key of the value to read. + /// + /// # Returns + /// + /// An `Option` containing the value if it exists, or `None` if it does not. + #[inline(always)] + fn read(ref self: StateChangeLog, key: felt252) -> Option { + match match_nullable(self.changes.get(key)) { + FromNullableResult::Null => { Option::None }, + FromNullableResult::NotNull(value) => Option::Some(value.unbox()), + } + } + + /// Writes a value to the StateChangeLog. + /// Values written to the StateChangeLog are not written to storage until the StateChangeLog is + /// totally finalized at the end of the transaction. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to a `StateChangeLog` instance. + /// * `key` - The key of the value to write. + /// * `value` - The value to write. + #[inline(always)] + fn write(ref self: StateChangeLog, key: felt252, value: T) { + self.changes.insert(key, NullableTrait::new(value)); + self.keyset.add(key); + } + + /// Creates a clone of the current StateChangeLog. + /// + /// # Arguments + /// + /// * `self` - A reference to the `StateChangeLog` instance to clone. + /// + /// # Returns + /// + /// A new `StateChangeLog` instance with the same contents as the original. + fn clone(ref self: StateChangeLog) -> StateChangeLog { + let mut cloned_changes = Default::default(); + let mut keyset_span = self.keyset.to_span(); + for key in keyset_span { + let value = self.changes.get(*key).deref(); + cloned_changes.insert(*key, NullableTrait::new(value)); + }; + + StateChangeLog { changes: cloned_changes, keyset: self.keyset.clone(), } + } +} + +#[derive(Default, Destruct)] +pub struct State { + /// Accounts states - without storage and balances, which are handled separately. + pub accounts: StateChangeLog, + /// Account storage states. `EthAddress` indicates the target contract, + /// `u256` indicates the storage key. + /// `u256` indicates the value stored. + /// We have to store the target contract, as we can't derive it from the + /// hashed address only when finalizing. + pub accounts_storage: StateChangeLog<(EthAddress, u256, u256)>, + /// Account states + /// Pending emitted events + pub events: Array, + /// Pending transfers + pub transfers: Array, + /// Account transient storage states. `EthAddress` indicates the target contract, + /// `u256` indicates the storage key. + /// `u256` indicates the value stored. + pub transient_account_storage: StateChangeLog<(EthAddress, u256, u256)>, +} + +#[generate_trait] +pub impl StateImpl of StateTrait { + /// Retrieves an account from the state, creating it if it doesn't exist. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `evm_address` - The EVM address of the account to retrieve. + /// + /// # Returns + /// + /// The `Account` associated with the given EVM address. + fn get_account(ref self: State, evm_address: EthAddress) -> Account { + let maybe_account = self.accounts.read(evm_address.into()); + match maybe_account { + Option::Some(acc) => { return acc; }, + Option::None => { + let account = AccountTrait::fetch_or_create(evm_address); + self.accounts.write(evm_address.into(), account); + return account; + } + } + } + + /// Sets an account in the state. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `account` - The `Account` to set. + #[inline(always)] + fn set_account(ref self: State, account: Account) { + let evm_address = account.evm_address(); + + self.accounts.write(evm_address.into(), account) + } + + /// Reads a value from the state for a given EVM address and key. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `evm_address` - The EVM address of the account. + /// * `key` - The storage key. + /// + /// # Returns + /// + /// The value stored at the given address and key. + #[inline(always)] + fn read_state(ref self: State, evm_address: EthAddress, key: u256) -> u256 { + let internal_key = compute_storage_key(evm_address, key); + let maybe_entry = self.accounts_storage.read(internal_key); + match maybe_entry { + Option::Some((_, _, value)) => { return value; }, + Option::None => { + let account = self.get_account(evm_address); + return fetch_original_storage(@account, key); + } + } + } + + /// Writes a value to the state for a given EVM address and key. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `evm_address` - The EVM address of the account. + /// * `key` - The storage key. + /// * `value` - The value to write. + #[inline(always)] + fn write_state(ref self: State, evm_address: EthAddress, key: u256, value: u256) { + let internal_key = compute_storage_key(evm_address, key); + self.accounts_storage.write(internal_key.into(), (evm_address, key, value)); + } + + /// Adds an event to the state. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `event` - The `Event` to add. + #[inline(always)] + fn add_event(ref self: State, event: Event) { + self.events.append(event) + } + + /// Adds a transfer to the state and updates account balances. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `transfer` - The `Transfer` to add. + /// + /// # Returns + /// + /// A `Result` indicating success or an `EVMError` if the transfer fails. + #[inline(always)] + fn add_transfer(ref self: State, transfer: Transfer) -> Result<(), EVMError> { + if (transfer.amount == 0 || transfer.sender.evm == transfer.recipient.evm) { + return Result::Ok(()); + } + let mut sender = self.get_account(transfer.sender.evm); + let mut recipient = self.get_account(transfer.recipient.evm); + + let (new_sender_balance, underflow) = sender.balance().overflowing_sub(transfer.amount); + ensure(!underflow, EVMError::InsufficientBalance)?; + + let (new_recipient_balance, overflow) = recipient.balance.overflowing_add(transfer.amount); + ensure(!overflow, EVMError::NumericOperations(BALANCE_OVERFLOW))?; + + sender.set_balance(new_sender_balance); + recipient.set_balance(new_recipient_balance); + + self.set_account(sender); + self.set_account(recipient); + + self.transfers.append(transfer); + Result::Ok(()) + } + + /// Reads a value from transient storage for a given EVM address and key. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `evm_address` - The EVM address of the account. + /// * `key` - The storage key. + /// + /// # Returns + /// + /// The value stored in transient storage at the given address and key. + #[inline(always)] + fn read_transient_storage(ref self: State, evm_address: EthAddress, key: u256) -> u256 { + let internal_key = compute_storage_key(evm_address, key); + let maybe_entry = self.transient_account_storage.read(internal_key); + match maybe_entry { + Option::Some((_, _, value)) => { return value; }, + Option::None => { return 0; } + } + } + + /// Writes a value to transient storage for a given EVM address and key. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `evm_address` - The EVM address of the account. + /// * `key` - The storage key. + /// * `value` - The value to write. + #[inline(always)] + fn write_transient_storage(ref self: State, evm_address: EthAddress, key: u256, value: u256) { + let internal_key = compute_storage_key(evm_address, key); + self.transient_account_storage.write(internal_key.into(), (evm_address, key, value)); + } + + /// Creates a clone of the current State. + /// + /// # Arguments + /// + /// * `self` - A reference to the `State` instance to clone. + /// + /// # Returns + /// + /// A new `State` instance with the same contents as the original. + #[inline(always)] + fn clone(ref self: State) -> State { + State { + accounts: self.accounts.clone(), + accounts_storage: self.accounts_storage.clone(), + events: self.events.clone(), + transfers: self.transfers.clone(), + transient_account_storage: self.transient_account_storage.clone(), + } + } + + /// Checks if an account is both in the global state and non-empty. + /// + /// # Arguments + /// + /// * `self` - A mutable reference to the `State` instance. + /// * `evm_address` - The EVM address of the account to check. + /// + /// # Returns + /// + /// `true` if the account exists and is non-empty, `false` otherwise. + #[inline(always)] + fn is_account_alive(ref self: State, evm_address: EthAddress) -> bool { + let account = self.get_account(evm_address); + return !(account.nonce == 0 && account.code.len() == 0 && account.balance == 0); + } +} + +/// Computes the key for the internal state for a given EVM storage key. +/// The key is computed as follows: +/// 1. Compute the hash of the EVM address and the key(low, high) using Poseidon. +/// 2. Return the hash +#[inline(always)] +pub fn compute_storage_key(evm_address: EthAddress, key: u256) -> felt252 { + let hash = PoseidonTrait::new().update_with(evm_address).update_with(key).finalize(); + hash +} + +/// Computes the storage address for a given EVM storage key. +/// The storage address is computed as follows: +/// 1. Compute the hash of the key (low, high) using Poseidon. +/// 2. Use `storage_base_address_from_felt252` to obtain the starknet storage base address. +/// Note: the storage_base_address_from_felt252 function always works for any felt - and returns the +/// number normalized into the range [0, 2^251 - 256). (x % (2^251 - 256)) +/// https://github.com/starkware-libs/cairo/issues/4187 +#[inline(always)] +pub fn compute_storage_address(key: u256) -> StorageBaseAddress { + let hash = PoseidonTrait::new().update_with(key).finalize(); + storage_base_address_from_felt252(hash) +} + +#[cfg(test)] +mod tests { + use crate::state::compute_storage_key; + use crate::test_utils; + + #[test] + fn test_compute_storage_key() { + let key = 100; + let evm_address = test_utils::evm_address(); + + // The values can be computed externally by running a Rust program using the + // `starknet_crypto` crate and `poseidon_hash_many`. + // ```rust + // use starknet_crypto::{FieldElement,poseidon_hash_many}; + // use crypto_bigint::{U256}; + + // fn main() { + // let keys: Vec = vec![ + // FieldElement::from_hex_be("0x65766d5f61646472657373").unwrap(), + // FieldElement::from_hex_be("0x64").unwrap(), + // FieldElement::from_hex_be("0x00").unwrap(), + // ]; + // let values_to_hash = [keys[0],keys[1],keys[2]]; + // let hash = poseidon_hash_many(&values_to_hash); + // + // } + // + let address = compute_storage_key(evm_address, key); + assert( + address == 0x1b0f25b79b18f8734761533714f234825f965d6215cebdc391ceb3b964dd36, + 'hash not expected value' + ) + } + + mod test_state_changelog { + use crate::state::{StateChangeLog, StateChangeLogTrait}; + use crate::test_utils; + use utils::set::SetTrait; + + #[test] + fn test_read_empty_log() { + let mut changelog: StateChangeLog = Default::default(); + let key = test_utils::storage_base_address().into(); + assert!(changelog.read(key).is_none()) + } + + #[test] + fn test_write_read() { + let mut changelog: StateChangeLog = Default::default(); + let key = test_utils::storage_base_address().into(); + + changelog.write(key, 42); + assert_eq!(changelog.read(key).unwrap(), 42); + assert_eq!(changelog.keyset.len(), 1); + + changelog.write(key, 43); + assert_eq!(changelog.read(key).unwrap(), 43); + assert_eq!(changelog.keyset.len(), 1); + + // Write multiple keys + let second_key = 'second_location'; + changelog.write(second_key, 1337.into()); + + assert_eq!(changelog.read(second_key).unwrap(), 1337); + assert_eq!(changelog.keyset.len(), 2); + } + } + + mod test_state { + use core::starknet::EthAddress; + use crate::model::account::{Account, AccountTrait}; + use crate::model::{Event, Transfer, Address}; + use crate::state::{State, StateTrait}; + use crate::test_utils; + use snforge_std::{test_address, start_mock_call}; + use utils::constants::EMPTY_KECCAK; + use utils::helpers::compute_starknet_address; + use utils::set::SetTrait; + use utils::traits::bytes::U8SpanExTrait; + + #[test] + fn test_get_account_when_inexistant() { + test_utils::setup_test_environment(); + let deployer = test_address(); + let mut state: State = Default::default(); + + // Transfer native tokens to sender + let evm_address: EthAddress = test_utils::evm_address(); + let starknet_address = compute_starknet_address( + deployer, evm_address, test_utils::uninitialized_account() + ); + let expected_account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + balance: 0, + selfdestruct: false, + is_created: false, + }; + + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 0); + let account = state.get_account(evm_address); + + assert_eq!(account, expected_account); + assert_eq!(state.accounts.keyset.len(), 1); + } + + #[test] + fn test_get_account_when_cached() { + test_utils::setup_test_environment(); + let deployer = test_address(); + let mut state: State = Default::default(); + + let evm_address: EthAddress = test_utils::evm_address(); + let starknet_address = compute_starknet_address( + deployer.into(), evm_address, test_utils::uninitialized_account() + ); + + let bytecode = [0xab, 0xcd, 0xef].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let expected_account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, + code: bytecode, + code_hash: code_hash, + nonce: 1, + balance: 420, + selfdestruct: false, + is_created: false, + }; + + state.set_account(expected_account); + let account = state.get_account(evm_address); + + assert_eq!(account, expected_account); + assert_eq!(state.accounts.keyset.len(), 1); + } + + #[test] + fn test_get_account_when_deployed() { + test_utils::setup_test_environment(); + let deployer = test_address(); + let mut state: State = Default::default(); + + let evm_address: EthAddress = test_utils::evm_address(); + let starknet_address = compute_starknet_address( + deployer, evm_address, test_utils::uninitialized_account() + ); + test_utils::register_account(evm_address, starknet_address); + let bytecode = [0xab, 0xcd, 0xef].span(); + let code_hash = bytecode.compute_keccak256_hash(); + let expected_account = Account { + address: Address { evm: evm_address, starknet: starknet_address }, + code: bytecode, + code_hash: code_hash, + nonce: 1, + balance: 420, + selfdestruct: false, + is_created: false, + }; + + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 420); + start_mock_call::< + Span + >(starknet_address, selector!("bytecode"), [0xab, 0xcd, 0xef].span()); + start_mock_call::(starknet_address, selector!("get_code_hash"), code_hash); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + let account = state.get_account(evm_address); + + assert_eq!(account, expected_account); + assert_eq!(state.accounts.keyset.len(), 1); + } + + #[test] + fn test_write_read_cached_storage() { + let mut state: State = Default::default(); + let evm_address: EthAddress = test_utils::evm_address(); + let key = 10; + let value = 100; + + state.write_state(evm_address, key, value); + let read_value = state.read_state(evm_address, key); + + assert(value == read_value, 'Storage mismatch'); + } + + #[test] + fn test_read_state_from_sn_storage() { + test_utils::setup_test_environment(); + let starknet_address = compute_starknet_address( + test_address(), test_utils::evm_address(), test_utils::uninitialized_account() + ); + test_utils::register_account(test_utils::evm_address(), starknet_address); + + let mut state: State = Default::default(); + let key = 10; + let value = 100; + + let bytecode = [0xab, 0xcd, 0xef].span(); + let code_hash = bytecode.compute_keccak256_hash(); + + start_mock_call::(starknet_address, selector!("storage"), value); + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 10000); + start_mock_call::>(starknet_address, selector!("bytecode"), bytecode); + start_mock_call::(starknet_address, selector!("get_code_hash"), code_hash); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + let read_value = state.read_state(test_utils::evm_address(), key); + + assert_eq!(value, read_value) + } + + #[test] + fn test_add_event() { + let mut state: State = Default::default(); + let event = Event { keys: array![100, 200], data: array![0xab, 0xde] }; + + state.add_event(event.clone()); + + assert(state.events.len() == 1, 'Event not added'); + assert(state.events[0].clone() == event, 'Event mismatch'); + } + + #[test] + fn test_add_transfer() { + //Given + let mut state: State = Default::default(); + test_utils::setup_test_environment(); + let deployer = test_address(); + + let sender_evm_address = test_utils::evm_address(); + let sender_starknet_address = compute_starknet_address( + deployer, sender_evm_address, test_utils::uninitialized_account() + ); + let sender_address = Address { + evm: sender_evm_address, starknet: sender_starknet_address + }; + + let recipient_evm_address = test_utils::other_evm_address(); + let recipient_starknet_address = compute_starknet_address( + deployer, recipient_evm_address, test_utils::uninitialized_account() + ); + let recipient_address = Address { + evm: recipient_evm_address, starknet: recipient_starknet_address + }; + let transfer = Transfer { + sender: sender_address, recipient: recipient_address, amount: 100 + }; + + // Write user balances in cache to avoid fetching from SN storage + let mut sender = Account { + address: sender_address, + balance: 300, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + selfdestruct: false, + is_created: false, + }; + state.set_account(sender); + let mut recipient = Account { + address: recipient_address, + balance: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + selfdestruct: false, + is_created: false, + }; + state.set_account(recipient); + + // When + state.add_transfer(transfer).unwrap(); + + // Then, transfer appended to log and cached balances updated + assert_eq!(state.transfers.len(), 1); + assert_eq!(*(state.transfers[0]), transfer); + + assert_eq!(state.get_account(sender_address.evm).balance(), 200); + assert_eq!(state.get_account(recipient_address.evm).balance(), 100); + } + + #[test] + fn test_add_transfer_with_same_sender_and_recipient() { + //Given + let mut state: State = Default::default(); + test_utils::setup_test_environment(); + let deployer = test_address(); + + let sender_evm_address = test_utils::evm_address(); + let sender_starknet_address = compute_starknet_address( + deployer, sender_evm_address, test_utils::uninitialized_account() + ); + let sender_address = Address { + evm: sender_evm_address, starknet: sender_starknet_address + }; + + // since sender and recipient is same + let transfer = Transfer { + sender: sender_address, recipient: sender_address, amount: 100 + }; + + // Write user balances in cache to avoid fetching from SN storage + let mut sender = Account { + address: sender_address, + balance: 300, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + selfdestruct: false, + is_created: false, + }; + state.set_account(sender); + + // When + state.add_transfer(transfer).unwrap(); + + // Then, no transfer appended to log and cached balances updated + assert_eq!(state.transfers.len(), 0); + + assert_eq!(state.get_account(sender_address.evm).balance(), 300); + } + + #[test] + fn test_add_transfer_when_amount_is_zero() { + //Given + let mut state: State = Default::default(); + test_utils::setup_test_environment(); + let deployer = test_address(); + + let sender_evm_address = test_utils::evm_address(); + let sender_starknet_address = compute_starknet_address( + deployer, sender_evm_address, test_utils::uninitialized_account() + ); + let sender_address = Address { + evm: sender_evm_address, starknet: sender_starknet_address + }; + + let recipient_evm_address = test_utils::other_evm_address(); + let recipient_starknet_address = compute_starknet_address( + deployer, recipient_evm_address, test_utils::uninitialized_account() + ); + let recipient_address = Address { + evm: recipient_evm_address, starknet: recipient_starknet_address + }; + let transfer = Transfer { + sender: sender_address, recipient: recipient_address, amount: 0 + }; + // Write user balances in cache to avoid fetching from SN storage + let mut sender = Account { + address: sender_address, + balance: 300, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + selfdestruct: false, + is_created: false, + }; + state.set_account(sender); + let mut recipient = Account { + address: recipient_address, + balance: 0, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + selfdestruct: false, + is_created: false, + }; + state.set_account(recipient); + + // When + state.add_transfer(transfer).unwrap(); + + // Then, no transfer appended to log and cached balances updated + assert_eq!(state.transfers.len(), 0); + assert_eq!(state.get_account(sender_address.evm).balance(), 300); + assert_eq!(state.get_account(recipient_address.evm).balance(), 0); + } + + #[test] + fn test_read_balance_cached() { + let mut state: State = Default::default(); + test_utils::setup_test_environment(); + let deployer = test_address(); + + let evm_address = test_utils::evm_address(); + let starknet_address = compute_starknet_address( + deployer, evm_address, test_utils::uninitialized_account() + ); + let address = Address { evm: evm_address, starknet: starknet_address }; + + let balance = 100; + + let mut account = Account { + address, + balance, + code: [].span(), + code_hash: EMPTY_KECCAK, + nonce: 0, + selfdestruct: false, + is_created: false, + }; + state.set_account(account); + let read_balance = state.get_account(address.evm).balance(); + + assert_eq!(balance, read_balance,); + } + + #[test] + fn test_read_balance_from_storage() { + test_utils::setup_test_environment(); + let deployer = test_address(); + let evm_address = test_utils::evm_address(); + let starknet_address = compute_starknet_address( + deployer, evm_address, test_utils::uninitialized_account() + ); + test_utils::register_account(evm_address, starknet_address); + + // Transfer native tokens to sender + let mut state: State = Default::default(); + let bytecode = [0xab, 0xcd, 0xef].span(); + let code_hash = bytecode.compute_keccak256_hash(); + start_mock_call::(test_utils::native_token(), selector!("balanceOf"), 10000); + start_mock_call::>(starknet_address, selector!("bytecode"), bytecode); + start_mock_call::(starknet_address, selector!("get_code_hash"), code_hash); + start_mock_call::(starknet_address, selector!("get_nonce"), 1); + let read_balance = state.get_account(evm_address).balance(); + + assert_eq!(read_balance, 10000); + } + + #[test] + fn test_write_read_transient_storage() { + let mut state: State = Default::default(); + let evm_address: EthAddress = test_utils::evm_address(); + let key = 10; + let value = 100; + + state.write_transient_storage(evm_address, key, value); + let read_value = state.read_transient_storage(evm_address, key); + + assert(value == read_value, 'Transient storage mismatch'); + } + } +} diff --git a/cairo/kakarot-ssj/crates/evm/src/test_data.cairo b/cairo/kakarot-ssj/crates/evm/src/test_data.cairo new file mode 100644 index 000000000..dff05e57a --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/test_data.cairo @@ -0,0 +1,2 @@ +pub mod test_data_blake2f; +pub mod test_data_modexp; diff --git a/cairo/kakarot-ssj/crates/evm/src/test_data/test_data_blake2f.cairo b/cairo/kakarot-ssj/crates/evm/src/test_data/test_data_blake2f.cairo new file mode 100644 index 000000000..2edd31c01 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/test_data/test_data_blake2f.cairo @@ -0,0 +1,1533 @@ +// source: [EIP-152](https://eips.ethereum.org/EIPS/eip-152), Test Vector 1 +pub fn blake2_precompile_fail_wrong_length_input_1_test_case() -> (Span, Span) { + let input = [ + 0, + 0, + 12, + 72, + 201, + 189, + 242, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 209, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 107, + 189, + 65, + 251, + 171, + 217, + 131, + 31, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91, + 97, + 98, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1 + ].span(); + + let output = [].span(); + + (input, output) +} + +// source: [EIP-152](https://eips.ethereum.org/EIPS/eip-152), Test Vector 2 +pub fn blake2_precompile_fail_wrong_length_input_2_test_case() -> (Span, Span) { + let input = [ + 0, + 0, + 0, + 0, + 12, + 72, + 201, + 189, + 242, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 209, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 107, + 189, + 65, + 251, + 171, + 217, + 131, + 31, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91, + 97, + 98, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1 + ].span(); + + let output = [].span(); + + (input, output) +} + +// source: [EIP-152](https://eips.ethereum.org/EIPS/eip-152), Test Vector 3 +pub fn blake2_precompile_fail_wrong_length_input_3_test_case() -> (Span, Span) { + let input = [ + 0, + 0, + 0, + 12, + 72, + 201, + 189, + 242, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 209, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 107, + 189, + 65, + 251, + 171, + 217, + 131, + 31, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91, + 97, + 98, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2 + ].span(); + + let output = [].span(); + + (input, output) +} + +// source: [EIP-152](https://eips.ethereum.org/EIPS/eip-152), Test Vector 4 +pub fn blake2_precompile_pass_0_test_case() -> (Span, Span) { + let input = [ + 0, + 0, + 0, + 0, + 72, + 201, + 189, + 242, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 209, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 107, + 189, + 65, + 251, + 171, + 217, + 131, + 31, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91, + 97, + 98, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1 + ].span(); + + let output = [ + 8, + 201, + 188, + 243, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 210, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 148, + 66, + 190, + 4, + 84, + 38, + 124, + 224, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91 + ].span(); + + (input, output) +} + + +// source: [EIP-152](https://eips.ethereum.org/EIPS/eip-152), Test Vector 5 +pub fn blake2_precompile_pass_1_test_case() -> (Span, Span) { + let input = [ + 0, + 0, + 0, + 12, + 72, + 201, + 189, + 242, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 209, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 107, + 189, + 65, + 251, + 171, + 217, + 131, + 31, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91, + 97, + 98, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1 + ].span(); + + let output = [ + 186, + 128, + 165, + 63, + 152, + 28, + 77, + 13, + 106, + 39, + 151, + 182, + 159, + 18, + 246, + 233, + 76, + 33, + 47, + 20, + 104, + 90, + 196, + 183, + 75, + 18, + 187, + 111, + 219, + 255, + 162, + 209, + 125, + 135, + 197, + 57, + 42, + 171, + 121, + 45, + 194, + 82, + 213, + 222, + 69, + 51, + 204, + 149, + 24, + 211, + 138, + 168, + 219, + 241, + 146, + 90, + 185, + 35, + 134, + 237, + 212, + 0, + 153, + 35 + ].span(); + + (input, output) +} + +// source: [EIP-152](https://eips.ethereum.org/EIPS/eip-152), Test Vector 6 +pub fn blake2_precompile_pass_2_test_case() -> (Span, Span) { + let input = [ + 0, + 0, + 0, + 12, + 72, + 201, + 189, + 242, + 103, + 230, + 9, + 106, + 59, + 167, + 202, + 132, + 133, + 174, + 103, + 187, + 43, + 248, + 148, + 254, + 114, + 243, + 110, + 60, + 241, + 54, + 29, + 95, + 58, + 245, + 79, + 165, + 209, + 130, + 230, + 173, + 127, + 82, + 14, + 81, + 31, + 108, + 62, + 43, + 140, + 104, + 5, + 155, + 107, + 189, + 65, + 251, + 171, + 217, + 131, + 31, + 121, + 33, + 126, + 19, + 25, + 205, + 224, + 91, + 97, + 98, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ].span(); + + let output = [ + 117, + 171, + 105, + 211, + 25, + 10, + 86, + 44, + 81, + 174, + 248, + 216, + 143, + 28, + 39, + 117, + 135, + 105, + 68, + 64, + 114, + 112, + 196, + 44, + 152, + 68, + 37, + 44, + 38, + 210, + 135, + 82, + 152, + 116, + 62, + 127, + 109, + 94, + 162, + 242, + 211, + 232, + 210, + 38, + 3, + 156, + 211, + 27, + 78, + 66, + 106, + 196, + 242, + 211, + 214, + 102, + 166, + 16, + 194, + 17, + 111, + 222, + 71, + 53 + ].span(); + + (input, output) +} diff --git a/cairo/kakarot-ssj/crates/evm/src/test_data/test_data_modexp.cairo b/cairo/kakarot-ssj/crates/evm/src/test_data/test_data_modexp.cairo new file mode 100644 index 000000000..c11e97103 --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/test_data/test_data_modexp.cairo @@ -0,0 +1,1553 @@ +fn test_modexp_modsize0_returndatasizeFiller_data() -> (Span, Span) { + let calldata = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 100, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 100, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 100, + 84, + 66, + 221, + 194, + 183, + 15, + 102, + 193, + 246, + 210, + 178, + 150, + 192, + 168, + 117, + 190, + 126, + 221, + 208, + 168, + 9, + 88, + 203, + 199, + 66, + 95, + 24, + 153, + 204, + 249, + 5, + 17, + 165, + 195, + 24, + 34, + 110, + 72, + 238, + 35, + 241, + 48, + 180, + 77, + 193, + 122, + 105, + 28, + 230, + 107, + 229, + 218, + 24, + 184, + 94, + 215, + 148, + 53, + 53, + 178, + 5, + 170, + 18, + 94, + 159, + 89, + 41, + 74, + 0, + 240, + 81, + 85, + 194, + 62, + 151, + 218, + 198, + 179, + 160, + 11, + 12, + 99, + 200, + 65, + 27, + 248, + 21, + 252, + 24, + 59, + 66, + 11, + 77, + 157, + 197, + 247, + 21, + 4, + 13, + 92, + 96, + 149, + 127, + 82, + 211, + 52, + 184, + 67, + 25, + 122, + 222, + 197, + 140, + 19, + 28, + 144, + 124, + 217, + 96, + 89, + 252, + 90, + 220, + 233, + 221, + 163, + 81, + 181, + 223, + 61, + 102, + 111, + 207, + 62, + 182, + 60, + 70, + 133, + 28, + 24, + 22, + 227, + 35, + 242, + 17, + 158, + 189, + 245, + 239, + 53 + ].span(); + + let expected = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ].span(); + + (calldata, expected) +} + +fn test_modexp_create2callPrecompiles_test0_berlin_data() -> (Span, Span) { + let calldata = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 3, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 254, + 255, + 255, + 252, + 46, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 47 + ].span(); + + let expected = [ + 22, + 46, + 173, + 130, + 202, + 222, + 250, + 234, + 246, + 233, + 40, + 50, + 72, + 253, + 242, + 242, + 132, + 95, + 99, + 150, + 246, + 241, + 124, + 77, + 90, + 57, + 248, + 32, + 182, + 246, + 181, + 249 + ].span(); + + (calldata, expected) +} + +fn test_modexp_eip198_example_1_data() -> (Span, Span) { + let calldata = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 3, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 254, + 255, + 255, + 252, + 46, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 254, + 255, + 255, + 252, + 47 + ].span(); + + let expected = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1 + ].span(); + + (calldata, expected) +} + + +fn test_modexp_eip198_example_2_data() -> (Span, Span) { + let calldata = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 254, + 255, + 255, + 252, + 46, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 254, + 255, + 255, + 252, + 47 + ].span(); + + let expected = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ].span(); + + (calldata, expected) +} + + +fn test_modexp_nagydani_1_square_data() -> (Span, Span) { + let calldata = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 224, + 154, + 217, + 103, + 84, + 101, + 197, + 58, + 16, + 159, + 172, + 102, + 164, + 69, + 201, + 27, + 41, + 45, + 43, + 178, + 197, + 38, + 138, + 221, + 179, + 12, + 216, + 47, + 128, + 252, + 176, + 3, + 63, + 249, + 124, + 128, + 165, + 252, + 111, + 57, + 25, + 58, + 233, + 105, + 198, + 237, + 230, + 113, + 10, + 107, + 122, + 194, + 112, + 120, + 160, + 109, + 144, + 239, + 28, + 114, + 229, + 200, + 95, + 181, + 2, + 252, + 158, + 31, + 107, + 235, + 129, + 81, + 101, + 69, + 151, + 82, + 24, + 7, + 94, + 194, + 175, + 17, + 140, + 216, + 121, + 141, + 246, + 224, + 138, + 20, + 124, + 96, + 253, + 96, + 149, + 172, + 43, + 176, + 44, + 41, + 8, + 207, + 77, + 215, + 200, + 31, + 17, + 194, + 137, + 228, + 188, + 233, + 143, + 53, + 83, + 118, + 143, + 57, + 42, + 128, + 206, + 34, + 191, + 92, + 79, + 74, + 36, + 140, + 107 + ].span(); + + let expected = [ + 96, + 0, + 143, + 22, + 20, + 204, + 1, + 220, + 251, + 107, + 251, + 9, + 198, + 37, + 207, + 144, + 180, + 125, + 68, + 104, + 219, + 129, + 181, + 248, + 183, + 163, + 157, + 66, + 243, + 50, + 234, + 185, + 178, + 218, + 143, + 45, + 149, + 49, + 22, + 72, + 168, + 242, + 67, + 244, + 187, + 19, + 207, + 179, + 216, + 247, + 242, + 163, + 192, + 20, + 18, + 46, + 187, + 62, + 212, + 27, + 2, + 120, + 58, + 220 + ].span(); + + (calldata, expected) +} + +fn test_modexp_nagydani_1_qube_data() -> (Span, Span) { + let calldata = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 224, + 154, + 217, + 103, + 84, + 101, + 197, + 58, + 16, + 159, + 172, + 102, + 164, + 69, + 201, + 27, + 41, + 45, + 43, + 178, + 197, + 38, + 138, + 221, + 179, + 12, + 216, + 47, + 128, + 252, + 176, + 3, + 63, + 249, + 124, + 128, + 165, + 252, + 111, + 57, + 25, + 58, + 233, + 105, + 198, + 237, + 230, + 113, + 10, + 107, + 122, + 194, + 112, + 120, + 160, + 109, + 144, + 239, + 28, + 114, + 229, + 200, + 95, + 181, + 3, + 252, + 158, + 31, + 107, + 235, + 129, + 81, + 101, + 69, + 151, + 82, + 24, + 7, + 94, + 194, + 175, + 17, + 140, + 216, + 121, + 141, + 246, + 224, + 138, + 20, + 124, + 96, + 253, + 96, + 149, + 172, + 43, + 176, + 44, + 41, + 8, + 207, + 77, + 215, + 200, + 31, + 17, + 194, + 137, + 228, + 188, + 233, + 143, + 53, + 83, + 118, + 143, + 57, + 42, + 128, + 206, + 34, + 191, + 92, + 79, + 74, + 36, + 140, + 107 + ].span(); + + let expected = [ + 72, + 52, + 164, + 107, + 165, + 101, + 219, + 39, + 144, + 59, + 28, + 114, + 12, + 157, + 89, + 62, + 132, + 228, + 203, + 214, + 173, + 46, + 100, + 179, + 24, + 133, + 217, + 68, + 246, + 140, + 216, + 1, + 249, + 34, + 37, + 168, + 150, + 28, + 149, + 45, + 223, + 39, + 151, + 250, + 71, + 1, + 179, + 48, + 200, + 92, + 75, + 54, + 55, + 152, + 16, + 11, + 146, + 26, + 26, + 34, + 164, + 106, + 127, + 236 + ].span(); + + (calldata, expected) +} diff --git a/cairo/kakarot-ssj/crates/evm/src/test_utils.cairo b/cairo/kakarot-ssj/crates/evm/src/test_utils.cairo new file mode 100644 index 000000000..4726c297d --- /dev/null +++ b/cairo/kakarot-ssj/crates/evm/src/test_utils.cairo @@ -0,0 +1,298 @@ +use contracts::kakarot_core::KakarotCore; +use core::ops::DerefMut; +use core::starknet::storage::{StoragePointerWriteAccess, StoragePathEntry}; +use core::starknet::storage_access::{StorageBaseAddress, storage_base_address_from_felt252}; +use core::starknet::{ContractAddress, EthAddress, contract_address_const, ClassHash}; +use core::traits::TryInto; +use crate::memory::{Memory, MemoryTrait}; + +use crate::model::vm::{VM, VMTrait}; +use crate::model::{Message, Environment, Address, AccountTrait}; +use snforge_std::start_cheat_chain_id_global; +use snforge_std::test_address; +use starknet::storage::StorageTraitMut; +use utils::constants; + +pub fn uninitialized_account() -> ClassHash { + 'uninitialized_account'.try_into().unwrap() +} + +pub fn account_contract() -> ClassHash { + 'account_contract'.try_into().unwrap() +} + + +pub fn setup_test_environment() { + let mut kakarot_core = KakarotCore::contract_state_for_testing(); + let mut kakarot_storage = kakarot_core.deref_mut().storage_mut(); + kakarot_storage.Kakarot_account_contract_class_hash.write(account_contract()); + kakarot_storage.Kakarot_uninitialized_account_class_hash.write(uninitialized_account()); + kakarot_storage.Kakarot_native_token_address.write(native_token()); + start_cheat_chain_id_global(chain_id().into()); +} + +pub fn register_account(evm_address: EthAddress, starknet_address: ContractAddress) { + let mut kakarot_core = KakarotCore::contract_state_for_testing(); + let mut kakarot_storage = kakarot_core.deref_mut().storage_mut(); + kakarot_storage.Kakarot_evm_to_starknet_address.entry(evm_address).write(starknet_address); +} + + +#[generate_trait] +pub impl MemoryUtilsImpl of MemoryTestUtilsTrait { + fn store_with_expansion(ref self: Memory, element: u256, offset: usize) { + self.ensure_length(offset + 32); + self.store(element, offset); + } + + fn store_n_with_expansion(ref self: Memory, elements: Span, offset: usize) { + self.ensure_length(offset + elements.len()); + self.store_n(elements, offset); + } +} + +#[derive(Destruct)] +struct VMBuilder { + vm: VM +} + +#[generate_trait] +pub impl VMBuilderImpl of VMBuilderTrait { + fn new() -> VMBuilder { + VMBuilder { vm: Default::default() }.with_gas_limit(0x1000000000000000) + } + + fn new_with_presets() -> VMBuilder { + VMBuilder { vm: preset_vm() } + } + + fn with_return_data(mut self: VMBuilder, return_data: Span) -> VMBuilder { + self.vm.set_return_data(return_data); + self + } + + fn with_caller(mut self: VMBuilder, address: Address) -> VMBuilder { + self.vm.message.caller = address; + self + } + + fn with_calldata(mut self: VMBuilder, calldata: Span) -> VMBuilder { + self.vm.message.data = calldata; + self + } + + fn with_read_only(mut self: VMBuilder) -> VMBuilder { + self.vm.message.read_only = true; + self + } + + fn with_bytecode(mut self: VMBuilder, bytecode: Span) -> VMBuilder { + self.vm.message.code = bytecode; + self + } + + fn with_gas_limit(mut self: VMBuilder, gas_limit: u64) -> VMBuilder { + self.vm.message.gas_limit = gas_limit; + self + } + + // pub fn with_nested_vm(mut self: VMBuilder) -> VMBuilder { + // let current_ctx = self.machine.current_ctx.unbox(); + + // // Second Execution Context + // let context_id = ExecutionContextType::Call(1); + // let mut child_context = preset_message(); + // child_context.ctx_type = context_id; + // child_context.parent_ctx = NullableTrait::new(current_ctx); + // let mut call_ctx = child_context.call_ctx(); + // call_ctx.caller = other_address(); + // child_context.call_ctx = BoxTrait::new(call_ctx); + // self.machine.current_ctx = BoxTrait::new(child_context); + // self + // } + + fn with_target(mut self: VMBuilder, target: Address) -> VMBuilder { + self.vm.message.target = target; + self + } + + fn build(mut self: VMBuilder) -> VM { + self.vm.valid_jumpdests = AccountTrait::get_jumpdests(self.vm.message.code); + return self.vm; + } + + fn with_gas_left(mut self: VMBuilder, gas_left: u64) -> VMBuilder { + self.vm.gas_left = gas_left; + self + } +} + +pub fn origin() -> EthAddress { + 'origin'.try_into().unwrap() +} + +pub fn dual_origin() -> Address { + let origin_evm = origin(); + let origin_starknet = utils::helpers::compute_starknet_address( + test_address(), origin_evm, uninitialized_account() + ); + Address { evm: origin_evm, starknet: origin_starknet } +} + +pub fn caller() -> EthAddress { + 'caller'.try_into().unwrap() +} + +pub fn coinbase() -> EthAddress { + 'coinbase'.try_into().unwrap() +} + +pub fn starknet_address() -> ContractAddress { + contract_address_const::<'starknet_address'>() +} + +pub fn evm_address() -> EthAddress { + 'evm_address'.try_into().unwrap() +} + +pub fn test_dual_address() -> Address { + Address { evm: evm_address(), starknet: starknet_address() } +} + +pub fn other_evm_address() -> EthAddress { + 'other_evm_address'.try_into().unwrap() +} + +pub fn other_starknet_address() -> ContractAddress { + contract_address_const::<'other_starknet_address'>() +} + +pub fn other_address() -> Address { + Address { evm: other_evm_address(), starknet: other_starknet_address() } +} + +pub fn storage_base_address() -> StorageBaseAddress { + storage_base_address_from_felt252('storage_base_address') +} + +pub fn zero_address() -> ContractAddress { + contract_address_const::<0x00>() +} + +pub fn callvalue() -> u256 { + 123456789 +} + +pub fn native_token() -> ContractAddress { + contract_address_const::<'native_token'>() +} + +pub fn chain_id() -> u64 { + 'KKRT'.try_into().unwrap() +} + +pub fn kakarot_address() -> ContractAddress { + contract_address_const::<'kakarot'>() +} + +pub fn sequencer_evm_address() -> EthAddress { + 'sequencer'.try_into().unwrap() +} + +pub fn eoa_address() -> EthAddress { + let evm_address: EthAddress = 0xe0a.try_into().unwrap(); + evm_address +} + +pub fn tx_gas_limit() -> u64 { + constants::BLOCK_GAS_LIMIT +} + +pub const BASE_FEE: u64 = 1000; + +pub fn gas_price() -> u128 { + BASE_FEE.into() + 1 +} + +pub fn value() -> u256 { + 0xffffffffffffffffffffffffffffffff +} + +pub fn ca_address() -> EthAddress { + let evm_address: EthAddress = 0xca.try_into().unwrap(); + evm_address +} + +pub fn preset_message() -> Message { + let code: Span = [0x00].span(); + let data: Span = [4, 5, 6].span(); + let value: u256 = callvalue(); + let caller = Address { + evm: origin(), + starknet: utils::helpers::compute_starknet_address( + test_address(), origin(), uninitialized_account() + ) + }; + let target = Address { + evm: evm_address(), + starknet: utils::helpers::compute_starknet_address( + test_address(), evm_address(), uninitialized_account() + ) + }; + let code_address = target; + let read_only = false; + let tx_gas_limit = tx_gas_limit(); + + Message { + target, + caller, + data, + value, + gas_limit: tx_gas_limit, + read_only, + code, + code_address, + should_transfer_value: true, + depth: 0, + accessed_addresses: Default::default(), + accessed_storage_keys: Default::default(), + } +} + +pub fn preset_environment() -> Environment { + let block_info = starknet::get_block_info().unbox(); + + Environment { + origin: dual_origin(), + gas_price: gas_price(), + chain_id: chain_id(), + prevrandao: 0, + block_number: block_info.block_number, + block_timestamp: block_info.block_timestamp, + block_gas_limit: constants::BLOCK_GAS_LIMIT, + coinbase: coinbase(), + base_fee: BASE_FEE, + state: Default::default(), + } +} + +pub fn preset_vm() -> VM { + let message = preset_message(); + let environment = preset_environment(); + let return_data = [1, 2, 3].span(); + VM { + stack: Default::default(), + memory: Default::default(), + pc: 0, + valid_jumpdests: AccountTrait::get_jumpdests(message.code), + return_data, + env: environment, + message, + gas_left: message.gas_limit, + running: true, + error: false, + accessed_addresses: Default::default(), + accessed_storage_keys: Default::default(), + gas_refund: 0, + } +} diff --git a/cairo/kakarot-ssj/crates/openzeppelin/Scarb.toml b/cairo/kakarot-ssj/crates/openzeppelin/Scarb.toml new file mode 100644 index 000000000..5b90de13d --- /dev/null +++ b/cairo/kakarot-ssj/crates/openzeppelin/Scarb.toml @@ -0,0 +1,32 @@ +# Due to the following error, we have to manually copy paste contracts from Open Zeppelin +# error: Version solving failed: +# - openzeppelin v0.7.0 (git+https:#github.com/OpenZeppelin/cairo-contracts.git?tag=v0.7.0#61a2505fe0c0f19b5de2b3f8dedf421ba2cff657) cannot use starknet v2.3.0-rc0 (std), because openzeppelin requires starknet >=2.2.0 + +# Scarb does not have real version solving algorithm yet. +# Perhaps in the future this conflict could be resolved, but currently, +# please upgrade your dependencies to use latest versions of their dependencies. +# +# +# Credits: "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" + + +[package] +name = "openzeppelin" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet.workspace = true + +[tool] +fmt.workspace = true + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } +assert_macros = "2.8.2" + +[scripts] +test = "snforge test --max-n-steps 4294967295" +test-profiling = "snforge test --max-n-steps 4294967295 --build-profile" diff --git a/cairo/kakarot-ssj/crates/openzeppelin/src/lib.cairo b/cairo/kakarot-ssj/crates/openzeppelin/src/lib.cairo new file mode 100644 index 000000000..79c66ba63 --- /dev/null +++ b/cairo/kakarot-ssj/crates/openzeppelin/src/lib.cairo @@ -0,0 +1 @@ +pub mod token; diff --git a/cairo/kakarot-ssj/crates/openzeppelin/src/token.cairo b/cairo/kakarot-ssj/crates/openzeppelin/src/token.cairo new file mode 100644 index 000000000..8f3777f6b --- /dev/null +++ b/cairo/kakarot-ssj/crates/openzeppelin/src/token.cairo @@ -0,0 +1 @@ +pub mod erc20; diff --git a/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20.cairo b/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20.cairo new file mode 100644 index 000000000..6779800b1 --- /dev/null +++ b/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20.cairo @@ -0,0 +1,6 @@ +pub mod erc20; +pub mod interface; + +pub use erc20::ERC20; +pub use interface::ERC20ABIDispatcher; +pub use interface::ERC20ABIDispatcherTrait; diff --git a/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20/erc20.cairo b/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20/erc20.cairo new file mode 100644 index 000000000..e5604d4ed --- /dev/null +++ b/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20/erc20.cairo @@ -0,0 +1,259 @@ +// We manually copy pasted contracts from Open Zeppelin +// We adapted it to 2.7.1 version of Cairo +// Credits: "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" + +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.7.0 (token/erc20/erc20.cairo) + +#[starknet::contract] +pub mod ERC20 { + use core::num::traits::Bounded; + use core::num::traits::Zero; + use core::starknet::ContractAddress; + use core::starknet::get_caller_address; + use core::starknet::storage::{ + Map, StorageMapWriteAccess, StorageMapReadAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, + }; + use openzeppelin::token::erc20::interface::{IERC20, IERC20CamelOnly}; + + #[storage] + struct Storage { + _name: felt252, + _symbol: felt252, + _total_supply: u256, + _balances: Map, + _allowances: Map<(ContractAddress, ContractAddress), u256>, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + } + + #[derive(Drop, starknet::Event)] + struct Transfer { + #[key] + from: ContractAddress, + #[key] + to: ContractAddress, + value: u256 + } + + #[derive(Drop, starknet::Event)] + struct Approval { + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + value: u256 + } + + mod Errors { + pub const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; + pub const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; + pub const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; + pub const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; + pub const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; + pub const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + initial_supply: u256, + recipient: ContractAddress + ) { + self.initializer(name, symbol); + self._mint(recipient, initial_supply); + } + + // + // External + // + + #[abi(embed_v0)] + impl ERC20Impl of IERC20 { + fn name(self: @ContractState) -> felt252 { + self._name.read() + } + + fn symbol(self: @ContractState) -> felt252 { + self._symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + 18 + } + + fn total_supply(self: @ContractState) -> u256 { + self._total_supply.read() + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self._balances.read(account) + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self._allowances.read((owner, spender)) + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + self._transfer(sender, recipient, amount); + true + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + let caller = get_caller_address(); + self._spend_allowance(sender, caller, amount); + self._transfer(sender, recipient, amount); + true + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let caller = get_caller_address(); + self._approve(caller, spender, amount); + true + } + } + + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl of IERC20CamelOnly { + fn totalSupply(self: @ContractState) -> u256 { + ERC20Impl::total_supply(self) + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC20Impl::balance_of(self, account) + } + + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + ERC20Impl::transfer_from(ref self, sender, recipient, amount) + } + } + + + #[abi(embed_v0)] + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) -> bool { + self._increase_allowance(spender, added_value) + } + + + #[abi(embed_v0)] + fn increaseAllowance( + ref self: ContractState, spender: ContractAddress, addedValue: u256 + ) -> bool { + increase_allowance(ref self, spender, addedValue) + } + + + #[abi(embed_v0)] + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) -> bool { + self._decrease_allowance(spender, subtracted_value) + } + + + #[abi(embed_v0)] + fn decreaseAllowance( + ref self: ContractState, spender: ContractAddress, subtractedValue: u256 + ) -> bool { + decrease_allowance(ref self, spender, subtractedValue) + } + + // + // Internal + // + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn initializer(ref self: ContractState, name: felt252, symbol: felt252) { + self._name.write(name); + self._symbol.write(symbol); + } + + fn _increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) -> bool { + let caller = get_caller_address(); + self._approve(caller, spender, self._allowances.read((caller, spender)) + added_value); + true + } + + fn _decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) -> bool { + let caller = get_caller_address(); + self + ._approve( + caller, spender, self._allowances.read((caller, spender)) - subtracted_value + ); + true + } + + fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); + self._total_supply.write(self._total_supply.read() + amount); + self._balances.write(recipient, self._balances.read(recipient) + amount); + self.emit(Transfer { from: Zero::zero(), to: recipient, value: amount }); + } + + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { + assert(!account.is_zero(), Errors::BURN_FROM_ZERO); + self._total_supply.write(self._total_supply.read() - amount); + self._balances.write(account, self._balances.read(account) - amount); + self.emit(Transfer { from: account, to: Zero::zero(), value: amount }); + } + + fn _approve( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + assert(!owner.is_zero(), Errors::APPROVE_FROM_ZERO); + assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO); + self._allowances.write((owner, spender), amount); + self.emit(Approval { owner, spender, value: amount }); + } + + fn _transfer( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); + assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); + self._balances.write(sender, self._balances.read(sender) - amount); + self._balances.write(recipient, self._balances.read(recipient) + amount); + self.emit(Transfer { from: sender, to: recipient, value: amount }); + } + + fn _spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + let current_allowance = self._allowances.read((owner, spender)); + if current_allowance != Bounded::MAX { + self._approve(owner, spender, current_allowance - amount); + } + } + } +} diff --git a/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20/interface.cairo b/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20/interface.cairo new file mode 100644 index 000000000..02c8c4f95 --- /dev/null +++ b/cairo/kakarot-ssj/crates/openzeppelin/src/token/erc20/interface.cairo @@ -0,0 +1,90 @@ +// Due to the following error, we have to manually copy paste contracts from Open Zeppelin +// error: Version solving failed: +// - openzeppelin v0.7.0 +// (git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.7.0#61a2505fe0c0f19b5de2b3f8dedf421ba2cff657) +// cannot use starknet v2.3.0-rc0 (std), because openzeppelin requires starknet >=2.2.0 + +// Scarb does not have real version solving algorithm yet. +// Perhaps in the future this conflict could be resolved, but currently, +// please upgrade your dependencies to use latest versions of their dependencies. +// Credits: "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" + +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.7.0 (token/erc20/interface.cairo) + +use core::starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +pub trait IERC20Camel { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +pub trait IERC20CamelOnly { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} + +#[starknet::interface] +pub trait ERC20ABI { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + fn increase_allowance(ref self: TState, spender: ContractAddress, added_value: u256) -> bool; + fn decrease_allowance( + ref self: TState, spender: ContractAddress, subtracted_value: u256 + ) -> bool; +} + +#[starknet::interface] +pub trait ERC20CamelABI { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + fn increaseAllowance(ref self: TState, spender: ContractAddress, addedValue: u256) -> bool; + fn decreaseAllowance(ref self: TState, spender: ContractAddress, subtractedValue: u256) -> bool; +} diff --git a/cairo/kakarot-ssj/crates/snforge_utils/.gitignore b/cairo/kakarot-ssj/crates/snforge_utils/.gitignore new file mode 100644 index 000000000..73aa31e60 --- /dev/null +++ b/cairo/kakarot-ssj/crates/snforge_utils/.gitignore @@ -0,0 +1,2 @@ +target +.snfoundry_cache/ diff --git a/cairo/kakarot-ssj/crates/snforge_utils/Scarb.toml b/cairo/kakarot-ssj/crates/snforge_utils/Scarb.toml new file mode 100644 index 000000000..baa7e7add --- /dev/null +++ b/cairo/kakarot-ssj/crates/snforge_utils/Scarb.toml @@ -0,0 +1,26 @@ +[package] +name = "snforge_utils" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet = "2.8.2" +evm = { path = "../evm" } + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } +assert_macros = "2.8.2" + +[[target.starknet-contract]] +sierra = true +casm = true +casm-add-pythonic-hints = true + + +[lib] +name = "snforge_utils" + +[scripts] +test = "snforge test" diff --git a/cairo/kakarot-ssj/crates/snforge_utils/src/contracts.cairo b/cairo/kakarot-ssj/crates/snforge_utils/src/contracts.cairo new file mode 100644 index 000000000..bcc872368 --- /dev/null +++ b/cairo/kakarot-ssj/crates/snforge_utils/src/contracts.cairo @@ -0,0 +1,51 @@ +#[starknet::interface] +trait IHello { + fn hello(self: @T); + fn bar(self: @T); +} + +#[starknet::contract] +mod HelloContract { + use super::{IHelloDispatcher, IHelloDispatcherTrait}; + use core::starknet::get_contract_address; + + #[storage] + struct Storage {} + + #[external(v0)] + fn hello(self: @ContractState) {} + + #[external(v0)] + fn bar(self: @ContractState) { + IHelloDispatcher { contract_address: get_contract_address() }.hello(); + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, DeclareResultTrait, ContractClassTrait}; + use snforge_utils::snforge_utils::{assert_called, assert_called_with}; + use super::{IHelloDispatcher, IHelloDispatcherTrait}; + + #[test] + fn test_calltrace_entrypoint() { + let helloclass = declare("HelloContract").unwrap().contract_class(); + let (hellocontract, _) = helloclass.deploy(@array![]).unwrap(); + + IHelloDispatcher { contract_address: hellocontract }.hello(); + + assert_called(hellocontract, selector!("hello")); + assert_called_with::<()>(hellocontract, selector!("hello"), ()); + } + + #[test] + fn test_calltrace_rec() { + let helloclass = declare("HelloContract").unwrap().contract_class(); + let (hellocontract, _) = helloclass.deploy(@array![]).unwrap(); + + IHelloDispatcher { contract_address: hellocontract }.bar(); + + assert_called(hellocontract, selector!("hello")); + assert_called_with::<()>(hellocontract, selector!("hello"), ()); + } +} diff --git a/cairo/kakarot-ssj/crates/snforge_utils/src/lib.cairo b/cairo/kakarot-ssj/crates/snforge_utils/src/lib.cairo new file mode 100644 index 000000000..eba213ef3 --- /dev/null +++ b/cairo/kakarot-ssj/crates/snforge_utils/src/lib.cairo @@ -0,0 +1,319 @@ +mod contracts; + +#[cfg(target: 'test')] +pub mod snforge_utils { + use core::array::ArrayTrait; + use core::option::OptionTrait; + use evm::state::compute_storage_key; + use starknet::ContractAddress; + use evm::model::Address; + use snforge_std::cheatcodes::storage::store_felt252; + use snforge_std::Event; + use snforge_std::cheatcodes::events::{Events}; + use array_utils::ArrayExtTrait; + + use snforge_std::trace::{get_call_trace, CallTrace, CallEntryPoint}; + + pub fn is_called(contract_address: ContractAddress, selector: felt252) -> bool { + let call_trace = get_call_trace(); + + // Check if the top-level call matches + if check_call_match(call_trace.entry_point, contract_address, selector) { + return true; + } + + // Check nested calls recursively + if check_nested_calls(call_trace.nested_calls, contract_address, selector) { + return true; + } + + false + } + + pub fn assert_called(contract_address: ContractAddress, selector: felt252) { + assert!(is_called(contract_address, selector), "Expected call not found in trace"); + } + + pub fn assert_not_called(contract_address: ContractAddress, selector: felt252) { + assert!(!is_called(contract_address, selector), "Unexpected call found in trace"); + } + + fn check_call_match( + entry_point: CallEntryPoint, contract_address: ContractAddress, selector: felt252 + ) -> bool { + entry_point.contract_address == contract_address + && entry_point.entry_point_selector == selector + } + + fn check_nested_calls( + calls: Array, contract_address: ContractAddress, selector: felt252 + ) -> bool { + let mut i = 0; + loop { + if i == calls.len() { + break false; + } + let call = calls.at(i).clone(); + if check_call_match(call.entry_point, contract_address, selector) { + break true; + } + if check_nested_calls(call.nested_calls, contract_address, selector) { + break true; + } + i += 1; + } + } + + pub fn assert_called_with, +Drop, +Copy>( + contract_address: ContractAddress, selector: felt252, calldata: C + ) { + let mut serialized_calldata = array![]; + Serde::serialize(@calldata, ref serialized_calldata); + assert!( + is_called_with(contract_address, selector, serialized_calldata.span()), + "Expected call with specific data not found in trace" + ); + } + + + pub fn is_called_with( + contract_address: ContractAddress, selector: felt252, calldata: Span + ) -> bool { + let call_trace = get_call_trace(); + + if check_call_match_with_data( + call_trace.entry_point, contract_address, selector, calldata + ) { + return true; + } + + check_nested_calls_with_data(call_trace.nested_calls, contract_address, selector, calldata) + } + + + fn check_call_match_with_data( + call: CallEntryPoint, + contract_address: ContractAddress, + selector: felt252, + calldata: Span + ) -> bool { + call.contract_address == contract_address + && call.entry_point_selector == selector + && call.calldata.span() == calldata + } + + fn check_nested_calls_with_data( + calls: Array, + contract_address: ContractAddress, + selector: felt252, + calldata: Span + ) -> bool { + let mut i = 0; + loop { + if i == calls.len() { + break false; + } + let call = calls.at(i).clone(); + if check_call_match_with_data(call.entry_point, contract_address, selector, calldata) { + break true; + } + if check_nested_calls_with_data( + call.nested_calls, contract_address, selector, calldata + ) { + break true; + } + i += 1; + } + } + + mod array_utils { + #[generate_trait] + pub impl ArrayExtImpl, +Drop> of ArrayExtTrait { + fn includes<+PartialEq>(self: @Array, item: T) -> bool { + let mut i = 0; + let mut found = false; + found = + loop { + if i == self.len() { + break false; + }; + if (*self.at(i)) == item { + break true; + } + i += 1; + }; + return found; + } + } + } + + /// A wrapper structure on an array of events emitted by a given contract. + #[derive(Drop, Clone)] + pub struct ContractEvents { + pub events: Span + } + + pub trait EventsFilterTrait { + fn emitted_by(self: @Events, contract_address: ContractAddress) -> EventsFilter; + } + + impl EventsFilterTraitImpl of EventsFilterTrait { + fn emitted_by(self: @Events, contract_address: ContractAddress) -> EventsFilter { + EventsFilter { + events: self, + contract_address: Option::Some(contract_address), + key_filter: Option::None, + data_filter: Option::None, + } + } + } + + #[derive(Copy, Drop)] + pub struct EventsFilter { + events: @Events, + contract_address: Option, + key_filter: Option>, + data_filter: Option, + } + + pub trait EventsFilterBuilderTrait { + fn from_events(events: @Events) -> EventsFilter; + fn with_contract_address( + self: EventsFilter, contract_address: ContractAddress + ) -> EventsFilter; + fn with_keys(self: EventsFilter, keys: Span) -> EventsFilter; + fn with_data(self: EventsFilter, data: felt252) -> EventsFilter; + fn build(self: @EventsFilter) -> ContractEvents; + } + + impl EventsFilterBuilderTraitImpl of EventsFilterBuilderTrait { + fn from_events(events: @Events) -> EventsFilter { + EventsFilter { + events: events, + contract_address: Option::None, + key_filter: Option::None, + data_filter: Option::None, + } + } + + fn with_contract_address( + mut self: EventsFilter, contract_address: ContractAddress + ) -> EventsFilter { + self.contract_address = Option::Some(contract_address); + self + } + + fn with_keys(mut self: EventsFilter, keys: Span) -> EventsFilter { + self.key_filter = Option::Some(keys); + self + } + + fn with_data(mut self: EventsFilter, data: felt252) -> EventsFilter { + self.data_filter = Option::Some(data); + self + } + + fn build(self: @EventsFilter) -> ContractEvents { + let events = (*self.events.events).span(); + let mut filtered_events = array![]; + + for i in 0 + ..events + .len() { + let (from, event) = events.at(i).clone(); + let mut include = true; + + if let Option::Some(addr) = self.contract_address { + if from != *addr { + include = false; + } + } + + if include && self.key_filter.is_some() { + if !(event.keys.span() == (*self.key_filter).unwrap()) { + include = false; + } + } + + if include && self.data_filter.is_some() { + if !event.data.includes((*self.data_filter).unwrap()) { + include = false; + } + } + + if include { + filtered_events.append(event.clone()); + } + }; + + ContractEvents { events: filtered_events.span() } + } + } + + pub trait ContractEventsTrait { + fn assert_emitted, impl TDrop: Drop>( + self: @ContractEvents, event: @T + ); + fn assert_not_emitted, impl TDrop: Drop>( + self: @ContractEvents, event: @T + ); + } + + impl ContractEventsTraitImpl of ContractEventsTrait { + fn assert_emitted, impl TDrop: Drop>( + self: @ContractEvents, event: @T + ) { + let mut expected_keys = array![]; + let mut expected_data = array![]; + event.append_keys_and_data(ref expected_keys, ref expected_data); + + let contract_events = (*self.events); + let mut found = false; + for i in 0 + ..contract_events + .len() { + let event = contract_events.at(i); + if event.keys == @expected_keys && event.data == @expected_data { + found = true; + break; + } + }; + + assert(found, 'Expected event was not emitted'); + } + + fn assert_not_emitted, impl TDrop: Drop>( + self: @ContractEvents, event: @T + ) { + let mut expected_keys = array![]; + let mut expected_data = array![]; + event.append_keys_and_data(ref expected_keys, ref expected_data); + + let contract_events = (*self.events); + for i in 0 + ..contract_events + .len() { + let event = contract_events.at(i); + assert( + event.keys != @expected_keys || event.data != @expected_data, + 'Unexpected event was emitted' + ); + } + } + } + + /// Stores a value in the EVM storage of a given Starknet contract. + pub fn store_evm(target: Address, evm_key: u256, evm_value: u256) { + let storage_address = compute_storage_key(target.evm, evm_key); + let serialized_value = [evm_value.low.into(), evm_value.high.into()].span(); + for offset in 0 + ..serialized_value + .len() { + store_felt252( + target.starknet, + storage_address + offset.into(), + *serialized_value.at(offset) + ); + }; + } +} diff --git a/cairo1_contracts/utils/.gitignore b/cairo/kakarot-ssj/crates/utils/.gitignore similarity index 100% rename from cairo1_contracts/utils/.gitignore rename to cairo/kakarot-ssj/crates/utils/.gitignore diff --git a/cairo/kakarot-ssj/crates/utils/Scarb.toml b/cairo/kakarot-ssj/crates/utils/Scarb.toml new file mode 100644 index 000000000..83548065f --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/Scarb.toml @@ -0,0 +1,25 @@ +[package] +name = "utils" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +evm = { path = "../evm" } +alexandria_data_structures = { path = "../alexandria_data_structures" } + +# For profiling +[cairo] +unstable-add-statements-functions-debug-info = true + +[tool] +fmt.workspace = true + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } +assert_macros = "2.8.2" + +[scripts] +test = "snforge test --max-n-steps 4294967295" +test-profiling = "snforge test --max-n-steps 4294967295 --build-profile" diff --git a/cairo/kakarot-ssj/crates/utils/src/address.cairo b/cairo/kakarot-ssj/crates/utils/src/address.cairo new file mode 100644 index 000000000..97a782c26 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/address.cairo @@ -0,0 +1,100 @@ +use core::array::ArrayTrait; +use core::starknet::EthAddress; +use core::traits::TryInto; +use crate::rlp::{RLPTrait, RLPItem}; +use crate::traits::bytes::{ToBytes, U8SpanExTrait}; +use crate::traits::eth_address::EthAddressExTrait; +use crate::traits::{TryIntoResult}; + +use evm::errors::EVMError; + +/// Computes the address of the new account that needs to be created. +/// +/// # Arguments +/// +/// * `sender_address` - The address of the account that wants to create the new account. +/// * `sender_nonce` - The transaction count of the account that wants to create the new account. +/// +/// # Returns +/// +/// The computed address of the new account as an `EthAddress`. +pub fn compute_contract_address(sender_address: EthAddress, sender_nonce: u64) -> EthAddress { + let mut sender_address: RLPItem = RLPItem::String(sender_address.to_bytes().span()); + let sender_nonce: RLPItem = RLPItem::String(sender_nonce.to_be_bytes()); + let computed_address = U8SpanExTrait::compute_keccak256_hash( + RLPTrait::encode_sequence([sender_address, sender_nonce].span()) + ); + let canonical_address = computed_address & 0xffffffffffffffffffffffffffffffffffffffff; + canonical_address.try_into().unwrap() +} + + +/// Computes the address of the new account that needs to be created, which is +/// based on the sender address, salt, and the bytecode. +/// +/// # Arguments +/// +/// * `sender_address` - The address of the account that wants to create the new account. +/// * `salt` - Address generation salt. +/// * `bytecode` - The bytecode of the new account to be created. +/// +/// # Returns +/// +/// A `Result` containing the computed address of the new account as an `EthAddress`, +/// or an `EVMError` if the conversion fails. +pub fn compute_create2_contract_address( + sender_address: EthAddress, salt: u256, bytecode: Span +) -> Result { + let hash = bytecode.compute_keccak256_hash().to_be_bytes_padded(); + + let sender_address = sender_address.to_bytes().span(); + + let salt = salt.to_be_bytes_padded(); + + let mut preimage: Array = array![]; + + preimage.append_span([0xff].span()); + preimage.append_span(sender_address); + preimage.append_span(salt); + preimage.append_span(hash); + + let address_hash = preimage.span().compute_keccak256_hash().to_be_bytes_padded(); + + let address: EthAddress = address_hash.slice(12, 20).try_into_result()?; + + Result::Ok(address) +} + +#[cfg(test)] +mod tests { + use contracts::test_data::counter_evm_bytecode; + use core::starknet::EthAddress; + use crate::address::{compute_contract_address, compute_create2_contract_address}; + + #[test] + fn test_compute_create2_contract_address() { + let bytecode = counter_evm_bytecode(); + let salt = 0xbeef; + let from: EthAddress = 0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266 + .try_into() + .expect('Wrong Eth address'); + + let address = compute_create2_contract_address(from, salt, bytecode) + .expect('create2_contract_address fail'); + + assert_eq!(address.into(), 0x088a44D7CdD8DEA4d1Db6E3F4059c70c405a0C97); + } + + #[test] + fn test_compute_contract_address() { + let nonce = 420; + let from: EthAddress = 0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266 + .try_into() + .expect('Wrong Eth address'); + + let address = compute_contract_address(from, nonce); + assert( + address.into() == 0x40A633EeF249F21D95C8803b7144f19AAfeEF7ae, 'wrong create address' + ); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/constants.cairo b/cairo/kakarot-ssj/crates/utils/src/constants.cairo new file mode 100644 index 000000000..0b4b1c676 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/constants.cairo @@ -0,0 +1,493 @@ +use crate::traits::{U8IntoEthAddress}; + +// FELT PRIME +// 2^251 + 17 * 2^192 + 1 +pub const FELT252_PRIME: u256 = 0x800000000000011000000000000000000000000000000000000000000000001; + +// Prefix used to compute the address of a Starknet contract being deployed. +// +pub const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS'; + + +// BLOCK +pub const BLOCK_GAS_LIMIT: u64 = 7_000_000; +pub const MIN_BASE_FEE_PER_BLOB_GAS: u64 = 1; +// CHAIN_ID = KKRT (0x4b4b5254) in ASCII +pub const CHAIN_ID: u64 = 1263227476; + +// STACK +pub const STACK_MAX_DEPTH: usize = 1024; + +// CODE +pub const MAX_CODE_SIZE: usize = 0x6000; +pub const MAX_INITCODE_SIZE: usize = 0x6000 * 2; + +// KECCAK +// The empty keccak256 hash, Solidity equivalent: +// contract EmptyHash { +// function emptyHash() public pure returns(bytes32) { +// return keccak256(""); +// } +// } +// Reproducing link: +// +pub const EMPTY_KECCAK: u256 = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + +pub const BURN_ADDRESS: felt252 = 0xdead; + + +// Numeric constants +pub const POW_256_0: u128 = 0x1; +pub const POW_256_1: u128 = 0x100; +pub const POW_256_2: u128 = 0x10000; +pub const POW_256_3: u128 = 0x1000000; +pub const POW_256_4: u128 = 0x100000000; +pub const POW_256_5: u128 = 0x10000000000; +pub const POW_256_6: u128 = 0x1000000000000; +pub const POW_256_7: u128 = 0x100000000000000; +pub const POW_256_8: u128 = 0x10000000000000000; +pub const POW_256_9: u128 = 0x1000000000000000000; +pub const POW_256_10: u128 = 0x100000000000000000000; +pub const POW_256_11: u128 = 0x10000000000000000000000; +pub const POW_256_12: u128 = 0x1000000000000000000000000; +pub const POW_256_13: u128 = 0x100000000000000000000000000; +pub const POW_256_14: u128 = 0x10000000000000000000000000000; +pub const POW_256_15: u128 = 0x1000000000000000000000000000000; +pub const POW_256_16: u256 = 0x100000000000000000000000000000000; + +pub const POW_256_REV: [ + u256 + ; 17] = [ + 0x100000000000000000000000000000000, + 0x1000000000000000000000000000000, + 0x10000000000000000000000000000, + 0x100000000000000000000000000, + 0x1000000000000000000000000, + 0x10000000000000000000000, + 0x100000000000000000000, + 0x1000000000000000000, + 0x10000000000000000, + 0x100000000000000, + 0x1000000000000, + 0x10000000000, + 0x100000000, + 0x1000000, + 0x10000, + 0x100, + 0x1 +]; + +pub const POW_2: [ + u128 + ; 128] = [ + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, + 0x80, + 0x100, + 0x200, + 0x400, + 0x800, + 0x1000, + 0x2000, + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, + 0x800000, + 0x1000000, + 0x2000000, + 0x4000000, + 0x8000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000, + 0x100000000, + 0x200000000, + 0x400000000, + 0x800000000, + 0x1000000000, + 0x2000000000, + 0x4000000000, + 0x8000000000, + 0x10000000000, + 0x20000000000, + 0x40000000000, + 0x80000000000, + 0x100000000000, + 0x200000000000, + 0x400000000000, + 0x800000000000, + 0x1000000000000, + 0x2000000000000, + 0x4000000000000, + 0x8000000000000, + 0x10000000000000, + 0x20000000000000, + 0x40000000000000, + 0x80000000000000, + 0x100000000000000, + 0x200000000000000, + 0x400000000000000, + 0x800000000000000, + 0x1000000000000000, + 0x2000000000000000, + 0x4000000000000000, + 0x8000000000000000, + 0x10000000000000000, + 0x20000000000000000, + 0x40000000000000000, + 0x80000000000000000, + 0x100000000000000000, + 0x200000000000000000, + 0x400000000000000000, + 0x800000000000000000, + 0x1000000000000000000, + 0x2000000000000000000, + 0x4000000000000000000, + 0x8000000000000000000, + 0x10000000000000000000, + 0x20000000000000000000, + 0x40000000000000000000, + 0x80000000000000000000, + 0x100000000000000000000, + 0x200000000000000000000, + 0x400000000000000000000, + 0x800000000000000000000, + 0x1000000000000000000000, + 0x2000000000000000000000, + 0x4000000000000000000000, + 0x8000000000000000000000, + 0x10000000000000000000000, + 0x20000000000000000000000, + 0x40000000000000000000000, + 0x80000000000000000000000, + 0x100000000000000000000000, + 0x200000000000000000000000, + 0x400000000000000000000000, + 0x800000000000000000000000, + 0x1000000000000000000000000, + 0x2000000000000000000000000, + 0x4000000000000000000000000, + 0x8000000000000000000000000, + 0x10000000000000000000000000, + 0x20000000000000000000000000, + 0x40000000000000000000000000, + 0x80000000000000000000000000, + 0x100000000000000000000000000, + 0x200000000000000000000000000, + 0x400000000000000000000000000, + 0x800000000000000000000000000, + 0x1000000000000000000000000000, + 0x2000000000000000000000000000, + 0x4000000000000000000000000000, + 0x8000000000000000000000000000, + 0x10000000000000000000000000000, + 0x20000000000000000000000000000, + 0x40000000000000000000000000000, + 0x80000000000000000000000000000, + 0x100000000000000000000000000000, + 0x200000000000000000000000000000, + 0x400000000000000000000000000000, + 0x800000000000000000000000000000, + 0x1000000000000000000000000000000, + 0x2000000000000000000000000000000, + 0x4000000000000000000000000000000, + 0x8000000000000000000000000000000, + 0x10000000000000000000000000000000, + 0x20000000000000000000000000000000, + 0x40000000000000000000000000000000, + 0x80000000000000000000000000000000 +]; + +pub const POW_2_256: [ + u256 + ; 256] = [ + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, + 0x80, + 0x100, + 0x200, + 0x400, + 0x800, + 0x1000, + 0x2000, + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, + 0x800000, + 0x1000000, + 0x2000000, + 0x4000000, + 0x8000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000, + 0x100000000, + 0x200000000, + 0x400000000, + 0x800000000, + 0x1000000000, + 0x2000000000, + 0x4000000000, + 0x8000000000, + 0x10000000000, + 0x20000000000, + 0x40000000000, + 0x80000000000, + 0x100000000000, + 0x200000000000, + 0x400000000000, + 0x800000000000, + 0x1000000000000, + 0x2000000000000, + 0x4000000000000, + 0x8000000000000, + 0x10000000000000, + 0x20000000000000, + 0x40000000000000, + 0x80000000000000, + 0x100000000000000, + 0x200000000000000, + 0x400000000000000, + 0x800000000000000, + 0x1000000000000000, + 0x2000000000000000, + 0x4000000000000000, + 0x8000000000000000, + 0x10000000000000000, + 0x20000000000000000, + 0x40000000000000000, + 0x80000000000000000, + 0x100000000000000000, + 0x200000000000000000, + 0x400000000000000000, + 0x800000000000000000, + 0x1000000000000000000, + 0x2000000000000000000, + 0x4000000000000000000, + 0x8000000000000000000, + 0x10000000000000000000, + 0x20000000000000000000, + 0x40000000000000000000, + 0x80000000000000000000, + 0x100000000000000000000, + 0x200000000000000000000, + 0x400000000000000000000, + 0x800000000000000000000, + 0x1000000000000000000000, + 0x2000000000000000000000, + 0x4000000000000000000000, + 0x8000000000000000000000, + 0x10000000000000000000000, + 0x20000000000000000000000, + 0x40000000000000000000000, + 0x80000000000000000000000, + 0x100000000000000000000000, + 0x200000000000000000000000, + 0x400000000000000000000000, + 0x800000000000000000000000, + 0x1000000000000000000000000, + 0x2000000000000000000000000, + 0x4000000000000000000000000, + 0x8000000000000000000000000, + 0x10000000000000000000000000, + 0x20000000000000000000000000, + 0x40000000000000000000000000, + 0x80000000000000000000000000, + 0x100000000000000000000000000, + 0x200000000000000000000000000, + 0x400000000000000000000000000, + 0x800000000000000000000000000, + 0x1000000000000000000000000000, + 0x2000000000000000000000000000, + 0x4000000000000000000000000000, + 0x8000000000000000000000000000, + 0x10000000000000000000000000000, + 0x20000000000000000000000000000, + 0x40000000000000000000000000000, + 0x80000000000000000000000000000, + 0x100000000000000000000000000000, + 0x200000000000000000000000000000, + 0x400000000000000000000000000000, + 0x800000000000000000000000000000, + 0x1000000000000000000000000000000, + 0x2000000000000000000000000000000, + 0x4000000000000000000000000000000, + 0x8000000000000000000000000000000, + 0x10000000000000000000000000000000, + 0x20000000000000000000000000000000, + 0x40000000000000000000000000000000, + 0x80000000000000000000000000000000, + 0x100000000000000000000000000000000, + 0x200000000000000000000000000000000, + 0x400000000000000000000000000000000, + 0x800000000000000000000000000000000, + 0x1000000000000000000000000000000000, + 0x2000000000000000000000000000000000, + 0x4000000000000000000000000000000000, + 0x8000000000000000000000000000000000, + 0x10000000000000000000000000000000000, + 0x20000000000000000000000000000000000, + 0x40000000000000000000000000000000000, + 0x80000000000000000000000000000000000, + 0x100000000000000000000000000000000000, + 0x200000000000000000000000000000000000, + 0x400000000000000000000000000000000000, + 0x800000000000000000000000000000000000, + 0x1000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, +]; + +pub const POW_2_0: u128 = 0x1; +pub const POW_2_8: u128 = 0x100; +pub const POW_2_16: u128 = 0x10000; +pub const POW_2_24: u128 = 0x1000000; +pub const POW_2_32: u128 = 0x100000000; +pub const POW_2_40: u128 = 0x10000000000; +pub const POW_2_48: u128 = 0x1000000000000; +pub const POW_2_53: u128 = 0x20000000000000; +pub const POW_2_56: u128 = 0x100000000000000; +pub const POW_2_64: u128 = 0x10000000000000000; +pub const POW_2_72: u128 = 0x1000000000000000000; +pub const POW_2_80: u128 = 0x100000000000000000000; +pub const POW_2_88: u128 = 0x10000000000000000000000; +pub const POW_2_96: u128 = 0x1000000000000000000000000; +pub const POW_2_104: u128 = 0x100000000000000000000000000; +pub const POW_2_112: u128 = 0x10000000000000000000000000000; +pub const POW_2_120: u128 = 0x1000000000000000000000000000000; +pub const POW_2_127: u128 = 0x80000000000000000000000000000000; + +pub const MAX_ADDRESS: u256 = 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; diff --git a/cairo/kakarot-ssj/crates/utils/src/crypto.cairo b/cairo/kakarot-ssj/crates/utils/src/crypto.cairo new file mode 100644 index 000000000..261fd6b16 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/crypto.cairo @@ -0,0 +1,2 @@ +pub mod blake2_compress; +pub mod modexp; diff --git a/cairo/kakarot-ssj/crates/utils/src/crypto/blake2_compress.cairo b/cairo/kakarot-ssj/crates/utils/src/crypto/blake2_compress.cairo new file mode 100644 index 000000000..fe20466ef --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/crypto/blake2_compress.cairo @@ -0,0 +1,170 @@ +use alexandria_data_structures::vec::{Felt252Vec, VecTrait}; +use core::num::traits::{BitSize, WrappingAdd}; +use crate::math::WrappingBitshift; + +const SIGMA_LINE_1: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; +const SIGMA_LINE_2: [usize; 16] = [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]; +const SIGMA_LINE_3: [usize; 16] = [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]; +const SIGMA_LINE_4: [usize; 16] = [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]; +const SIGMA_LINE_5: [usize; 16] = [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]; +const SIGMA_LINE_6: [usize; 16] = [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]; +const SIGMA_LINE_7: [usize; 16] = [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]; +const SIGMA_LINE_8: [usize; 16] = [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]; +const SIGMA_LINE_9: [usize; 16] = [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]; +const SIGMA_LINE_10: [usize; 16] = [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]; + +const IV_DATA: [ + u64 + ; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +/// SIGMA from [spec](https://datatracker.ietf.org/doc/html/rfc7693#section-2.7) +fn SIGMA() -> Span> { + [ + SIGMA_LINE_1.span(), + SIGMA_LINE_2.span(), + SIGMA_LINE_3.span(), + SIGMA_LINE_4.span(), + SIGMA_LINE_5.span(), + SIGMA_LINE_6.span(), + SIGMA_LINE_7.span(), + SIGMA_LINE_8.span(), + SIGMA_LINE_9.span(), + SIGMA_LINE_10.span(), + ].span() +} + +/// got IV from [here](https://en.wikipedia.org/wiki/BLAKE_(hash_function)) +fn IV() -> Span { + IV_DATA.span() +} + +fn rotate_right(value: u64, n: u32) -> u64 { + if n == 0 { + value // No rotation needed + } else { + let bits = BitSize::::bits(); // The number of bits in a u64 + let n = n % bits; // Ensure n is less than 64 + + let res = value.wrapping_shr(n) | value.wrapping_shl((bits - n)); + res + } +} + +/// compression function: [see](https://datatracker.ietf.org/doc/html/rfc7693#section-3.2) +/// # Parameters +/// * `rounds` - number of rounds for mixing +/// * `h` - state vector +/// * `m` - message block, padded with 0s to full block size +/// * `t` - 2w-bit counter +/// * `f` - final block indicator flag +/// # Returns +/// updated state vector +pub fn compress(rounds: usize, h: Span, m: Span, t: Span, f: bool) -> Span { + let mut v = VecTrait::::new(); + for _ in 0..16_u8 { + v.push(0); + }; + + let IV = IV(); + + let mut i = 0; + loop { + if (i == h.len()) { + break; + } + + v.set(i, *h[i]); + v.set(i + h.len(), *IV[i]); + + i += 1; + }; + + v.set(12, v[12] ^ *t[0]); + v.set(13, v[13] ^ *t[1]); + + if f { + v.set(14, ~v[14]); + } + + let mut i = 0; + loop { + if i == rounds { + break; + } + + let s = *(SIGMA()[i % 10]); + + g(ref v, 0, 4, 8, 12, *m[*s[0]], *m[*s[1]]); + g(ref v, 1, 5, 9, 13, *m[*s[2]], *m[*s[3]]); + g(ref v, 2, 6, 10, 14, *m[*s[4]], *m[*s[5]]); + g(ref v, 3, 7, 11, 15, *m[*s[6]], *m[*s[7]]); + + g(ref v, 0, 5, 10, 15, *m[*s[8]], *m[*s[9]]); + g(ref v, 1, 6, 11, 12, *m[*s[10]], *m[*s[11]]); + g(ref v, 2, 7, 8, 13, *m[*s[12]], *m[*s[13]]); + g(ref v, 3, 4, 9, 14, *m[*s[14]], *m[*s[15]]); + + i += 1; + }; + + let mut result: Array = Default::default(); + + let mut i = 0; + loop { + if (i == 8) { + break; + } + + result.append(*h[i] ^ (v[i] ^ v[i + 8])); + + i += 1; + }; + + result.span() +} + + +/// Mixing Function G +/// It mixes input words into four words indexed by "a", "b", "c", and "d" in the working vector, +/// see [spec](https://datatracker.ietf.org/doc/html/rfc7693#section-3.1) +/// +/// # Parameters +/// * `v` - working vector +/// * `a`- index of word v[a] +/// * `b`- index of word v[b] +/// * `c`- index of word v[c] +/// * `d`- index of word v[d] +/// * `x` - input word x to be used for mixing +/// * `y` - input word y to be used for mixing +fn g(ref v: Felt252Vec, a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + let mut v_a = v[a]; + let mut v_b = v[b]; + let mut v_c = v[c]; + let mut v_d = v[d]; + + let tmp = v_a.wrapping_add(v_b); + v_a = tmp.wrapping_add(x); + v_d = rotate_right(v_d ^ v_a, 32); + v_c = v_c.wrapping_add(v_d); + v_b = rotate_right(v_b ^ v_c, 24); + + let tmp = v_a.wrapping_add(v_b); + v_a = tmp.wrapping_add(y); + v_d = rotate_right(v_d ^ v_a, 16); + v_c = v_c.wrapping_add(v_d); + v_b = rotate_right(v_b ^ v_c, 63); + + v.set(a, v_a); + v.set(b, v_b); + v.set(c, v_c); + v.set(d, v_d); +} diff --git a/cairo/kakarot-ssj/crates/utils/src/crypto/modexp.cairo b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp.cairo new file mode 100644 index 000000000..0fde2f4c2 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp.cairo @@ -0,0 +1,3 @@ +pub mod arith; +pub mod lib; +pub mod mpnat; diff --git a/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/arith.cairo b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/arith.cairo new file mode 100644 index 000000000..95a53bd37 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/arith.cairo @@ -0,0 +1,2524 @@ +// CREDITS: The implementation has been take from +// [aurora-engine](https://github.com/aurora-is-near/aurora-engine/tree/develop/engine-modexp) +use alexandria_data_structures::vec::{Felt252Vec, Felt252VecImpl}; + +use core::num::traits::{WideMul, OverflowingAdd, OverflowingSub, WrappingMul}; +use core::option::OptionTrait; +use crate::felt_vec::{Felt252VecTrait}; +use crate::helpers::{u128_split}; +use crate::math::WrappingBitshift; +use crate::math::{Bitshift}; +use crate::traits::BoolIntoNumeric; +use crate::traits::integer::U128Trait; +use super::mpnat::{MPNat, Word, DoubleWord, WORD_BITS, BASE, DOUBLE_WORD_MAX, WORD_MAX}; + +// Computes the "Montgomery Product" of two numbers. +// See Coarsely Integrated Operand Scanning (CIOS) Method in +// https://www.microsoft.com/en-us/research/wp-content/uploads/1996/01/j37acmon.pdf +// In short, computes `xy (r^-1) mod n`, where `r = 2^(8*4*s)` and `s` is the number of +// digits needs to represent `n`. `n_prime` has the property that `r(r^(-1)) - nn' = 1`. +// Note: This algorithm only works if `xy < rn` (generally we will either have both `x < n`, `y < n` +// or we will have `x < r`, `y < n`). +pub fn monpro(ref x: MPNat, ref y: MPNat, ref n: MPNat, n_prime: Word, ref out: Felt252Vec) { + let s = out.len() - 2; + + let mut i = 0; + loop { + if i == s { + break; + } + + let mut c = 0; + let mut j = 0; + + loop { + if j == s { + break; + } + + let (prod, carry) = shifted_carrying_mul( + out[j], x.digits.get(j).unwrap_or(0), y.digits.get(i).unwrap_or(0), c, + ); + out.set(j, prod); + c = carry; + + j += 1; + }; + + let (sum, carry) = carrying_add(out[s], c, false); + out.set(s, sum); + out.set(s + 1, carry.into()); + + let m = out[0].wrapping_mul(n_prime); + let (_, carry) = shifted_carrying_mul(out[0], m, n.digits.get(0).unwrap_or(0), 0); + c = carry; + + let mut j = 1; + loop { + if j == s { + break; + } + + let (prod, carry) = shifted_carrying_mul(out[j], m, n.digits.get(j).unwrap_or(0), c); + out.set(j - 1, prod); + c = carry; + + j += 1; + }; + + let (sum, carry) = carrying_add(out[s], c, false); + out.set(s - 1, sum); + out.set(s, out[s + 1] + (carry.into())); // overflow impossible at this stage + + i += 1; + }; + + // Result is only in the first s + 1 words of the output. + out.set(s + 1, 0); + + let mut j = s + 1; + let should_return = loop { + if j == 0 { + break false; + } + + let i = j - 1; + + if out[i] > n.digits.get(i).unwrap_or(0) { + break false; + } else if out[i] < n.digits.get(i).unwrap_or(0) { + break true; + } + + j -= 1; + }; + + if should_return { + return; + } + + let mut b = false; + let mut i: u32 = 0; + loop { + if i == s || i == out.len { + break; + } + + let out_digit = out[i]; + + let (diff, borrow) = borrowing_sub(out_digit, n.digits.get(i).unwrap_or(0), b); + out.set(i, diff); + b = borrow; + + i += 1; + }; + + let (diff, _) = borrowing_sub(out[s], 0, b); + out.set(s, diff); +} + +// Equivalent to `monpro(x, x, n, n_prime, out)`, but more efficient. +pub fn monsq(ref x: MPNat, ref n: MPNat, n_prime: Word, ref out: Felt252Vec) { + let s = n.digits.len(); + + big_sq(ref x, ref out); + let mut i = 0; + + loop { + if i == s { + break false; + } + + let mut c: Word = 0; + let m = out[i].wrapping_mul(n_prime); + + let mut j = 0; + loop { + if j == s { + break; + } + + let (prod, carry) = shifted_carrying_mul( + out[i + j], m, n.digits.get(j).unwrap_or(0), c + ); + out.set(i + j, prod); + c = carry; + + j += 1; + }; + + let mut j = i + s; + loop { + if c == 0 { + break; + } + let (sum, carry) = carrying_add(out[j], c, false); + out.set(j, sum); + c = carry.into(); + + j += 1; + }; + + i += 1; + }; + + // Only keep the last `s + 1` digits in `out`. + let mut new_vec: Felt252Vec = out.clone_slice(s, s + 1); + + // safe unwrap, since new_vec.len <= out.len + new_vec.expand(out.len).unwrap(); + out = new_vec; + + let mut k = s + 1; + let should_return = loop { + if k == 0 { + break false; + } + + let i = k - 1; + + if out[i] < n.digits.get(i).unwrap_or(0) { + break true; + } + if out[i] > n.digits.get(i).unwrap_or(0) { + break false; + } + + k -= 1; + }; + + if should_return { + return; + } + + let mut b = false; + let mut i = 0; + loop { + if i == s || i == out.len { + break; + } + + let out_digit = out[i]; + let (diff, borrow) = borrowing_sub(out_digit, n.digits.get(i).unwrap_or(0), b); + out.set(i, diff); + b = borrow; + + i += 1; + }; + + let (diff, _) = borrowing_sub(out[s], 0, b); + out.set(s, diff); +} + + +/// Computes `base ^ exp`, ignoring overflow. +pub fn big_wrapping_pow( + ref base: MPNat, exp: Span, ref scratch_space: Felt252Vec +) -> MPNat { + let mut digits = Felt252VecImpl::new(); + digits.expand(scratch_space.len()).unwrap(); + let mut result = MPNat { digits }; + result.digits.set(0, 1); + + let mut i = 0; + loop { + if i == exp.len() { + break; + } + + let b = *exp[i]; + let mut mask: u8 = 128; + + loop { + if mask == 0 { + break; + } + + // TODO: investigae if deep clone can be avoided + let digits = result.digits.duplicate(); + let mut tmp = MPNat { digits }; + + big_wrapping_mul(ref result, ref tmp, ref scratch_space); + result.digits.copy_from_vec_le(ref scratch_space).unwrap(); + scratch_space.reset(); // zero-out the scratch space + + if (b & mask) != 0 { + big_wrapping_mul(ref result, ref base, ref scratch_space); + result.digits.copy_from_vec_le(ref scratch_space).unwrap(); + scratch_space.reset(); // zero-out the scratch space + } + + mask = mask.shr(1); + }; + + i += 1; + }; + + result +} + +/// Computes `(x * y) mod 2^(WORD_BITS*out.len())`. +pub fn big_wrapping_mul(ref x: MPNat, ref y: MPNat, ref out: Felt252Vec) { + let s = out.len(); + let mut i = 0; + + loop { + if i == s { + break; + } + + let mut c: Word = 0; + + let mut j = 0; + let stop_condition = s - i; + loop { + if j == stop_condition { + break; + } + + let (prod, carry) = shifted_carrying_mul( + out[i + j], x.digits.get(j).unwrap_or(0), y.digits.get(i).unwrap_or(0), c + ); + c = carry; + out.set(i + j, prod); + + j += 1; + }; + + i += 1; + } +} + +// Given x odd, computes `x^(-1) mod 2^32`. +// See `MODULAR-INVERSE` in https://link.springer.com/content/pdf/10.1007/3-540-46877-3_21.pdf +pub fn mod_inv(x: Word) -> Word { + let mut y = 1; + let mut i = 2; + + loop { + if i == WORD_BITS { + break; + } + + let mask: u64 = 1_u64.shl(i) - 1; + let xy = x.wrapping_mul(y) & mask; + let q = (mask + 1) / 2; + if xy >= q { + y += q; + } + i += 1; + }; + + let xy = x.wrapping_mul(y); + let q = 1_u64.wrapping_shl((WORD_BITS - 1)); + if xy >= q { + y += q; + } + y +} + +/// Computes R mod n, where R = 2^(WORD_BITS*k) and k = n.digits.len() +/// Note that if R = qn + r, q must be smaller than 2^WORD_BITS since `2^(WORD_BITS) * n > R` +/// (adding a whole additional word to n is too much). +/// Uses the two most significant digits of n to approximate the quotient, +/// then computes the difference to get the remainder. It is possible that this +/// quotient is too big by 1; we can catch that case by looking for overflow +/// in the subtraction. +pub fn compute_r_mod_n(ref n: MPNat, ref out: Felt252Vec) { + let k = n.digits.len(); + + if k == 1 { + let r = BASE; + let result = r % (n.digits[0].into()); + out.set(0, result.as_u64()); + return; + } + + let approx_n = join_as_double(n.digits[k - 1], n.digits[k - 2]); + let approx_q = DOUBLE_WORD_MAX / approx_n; + let mut approx_q: Word = approx_q.as_u64(); + + loop { + let mut c = 0; + let mut b = false; + + let mut i: usize = 0; + loop { + if i == n.digits.len || i == out.len { + break; + } + + let n_digit = n.digits[i]; + + let (prod, carry) = carrying_mul(approx_q, n_digit, c); + c = carry; + + let (diff, borrow) = borrowing_sub(0, prod, b); + b = borrow; + out.set(i, diff); + + i += 1; + }; + + let (_, borrow) = borrowing_sub(1, c, b); + if borrow { + // approx_q was too large so `R - approx_q*n` overflowed. + // try again with approx_q -= 1 + approx_q -= 1; + } else { + break; + } + } +} + +/// Computes `a + xy + c` where any overflow is captured as the "carry", +/// the second part of the output. The arithmetic in this function is +/// guaranteed to never overflow because even when all 4 variables are +/// equal to `WORD_MAX` the output is smaller than `DOUBLEWORD_MAX`. +pub fn shifted_carrying_mul(a: Word, x: Word, y: Word, c: Word) -> (Word, Word) { + let res: DoubleWord = a.into() + x.wide_mul(y) + c.into(); + let (top_word, bottom_word) = u128_split(res); + (bottom_word, top_word) +} + +/// Computes `xy + c` where any overflow is captured as the "carry", +/// the second part of the output. The arithmetic in this function is +/// guaranteed to never overflow because even when all 3 variables are +/// equal to `Word::MAX` the output is smaller than `DoubleWord::MAX`. +pub fn carrying_mul(x: Word, y: Word, c: Word) -> (Word, Word) { + let wide = x.wide_mul(y) + c.into(); + let (top_word, bottom_word) = u128_split(wide); + (bottom_word, top_word) +} + +/// computes x + y accounting for any carry from a previous addition +pub fn carrying_add(x: Word, y: Word, carry: bool) -> (Word, bool) { + let (a, b) = x.overflowing_add(y); + let (c, d) = a.overflowing_add(carry.into()); + (c, b | d) +} + +// Computes `x - y` accounting for any borrow from a previous subtraction +pub fn borrowing_sub(x: Word, y: Word, borrow: bool) -> (Word, bool) { + let (a, b) = x.overflowing_sub(y); + let (c, d) = a.overflowing_sub(borrow.into()); + (c, b | d) +} + +/// Takes 2 words and joins them into a double word +/// +/// # Arguments +/// `hi` is the most significant word +/// `lo` is the least significant word +/// +/// # Returns +/// The double word obtained by joining `hi` and `lo` +pub fn join_as_double(hi: Word, lo: Word) -> DoubleWord { + let hi: DoubleWord = hi.into(); + hi.shl(WORD_BITS).into() + lo.into() +} + +/// Computes `x^2`, storing the result in `out`. +fn big_sq(ref x: MPNat, ref out: Felt252Vec) { + let s = x.digits.len(); + let mut i = 0; + + loop { + if i == s { + break; + } + + let (product, carry) = shifted_carrying_mul(out[i + i], x.digits[i], x.digits[i], 0); + out.set(i + i, product); + let mut c: DoubleWord = carry.into(); + + let mut j = i + 1; + + loop { + if j == s { + break; + } + + let mut new_c: DoubleWord = 0; + let res: DoubleWord = (x.digits[i].into()) * (x.digits[j].into()); + let (res, overflow) = res.overflowing_add(res); + if overflow { + new_c += BASE; + } + + let (res, overflow) = out[i + j].into().overflowing_add(res); + if overflow { + new_c += BASE; + } + + let (res, overflow) = res.overflowing_add(c); + if overflow { + new_c += BASE; + } + + out.set(i + j, res.as_u64()); + c = new_c + res.shr(WORD_BITS); + + j += 1; + }; + + let (sum, carry) = carrying_add(out[i + s], c.as_u64(), false); + out.set(i + s, sum); + out.set(i + s + 1, (c.shr(WORD_BITS) + (carry.into())).as_u64()); + + i += 1; + } +} + +// Performs `a <<= shift`, returning the overflow +pub fn in_place_shl(ref a: Felt252Vec, shift: u32) -> Word { + let mut c: Word = 0; + let carry_shift = WORD_BITS - shift; + + let mut i = 0; + loop { + if i == a.len { + break; + } + + let mut a_digit = a[i]; + let carry = a_digit.wrapping_shr(carry_shift); + a_digit = a_digit.wrapping_shl(shift) | c; + a.set(i, a_digit); + + c = carry; + + i += 1; + }; + + c +} + +// Performs `a >>= shift`, returning the overflow +pub fn in_place_shr(ref a: Felt252Vec, shift: u32) -> Word { + let mut b: Word = 0; + let borrow_shift = WORD_BITS - shift; + + let mut i = a.len; + loop { + if i == 0 { + break; + } + + let j = i - 1; + + let mut a_digit = a[j]; + let borrow = a_digit.wrapping_shl(borrow_shift); + a_digit = a_digit.wrapping_shr(shift) | b; + a.set(j, a_digit); + + b = borrow; + + i -= 1; + }; + + b +} + +// Performs a += b, returning if there was overflow +pub fn in_place_add(ref a: Felt252Vec, ref b: Felt252Vec) -> bool { + let mut c = false; + + let mut i = 0; + + loop { + if i == a.len() || i == b.len() { + break; + } + + let a_digit = a[i]; + let b_digit = b[i]; + + let (sum, carry) = carrying_add(a_digit, b_digit, c); + a.set(i, sum); + c = carry; + + i += 1; + }; + + c +} + +// Performs `a -= xy`, returning the "borrow". +pub fn in_place_mul_sub(ref a: Felt252Vec, ref x: Felt252Vec, y: Word) -> Word { + // a -= x*0 leaves a unchanged, so return early + if y == 0 { + return 0; + } + + // carry is between -big_digit::MAX and 0, so to avoid overflow we store + // offset_carry = carry + big_digit::MAX + let mut offset_carry = WORD_MAX; + + let mut i = 0; + + loop { + if i == a.len() || i == x.len() { + break; + } + + let a_digit = a[i]; + let x_digit = x[i]; + + // We want to calculate sum = x - y * c + carry. + // sum >= -(big_digit::MAX * big_digit::MAX) - big_digit::MAX + // sum <= big_digit::MAX + // Offsetting sum by (big_digit::MAX << big_digit::BITS) puts it in DoubleBigDigit range. + let offset_sum = join_as_double(WORD_MAX, a_digit) + - WORD_MAX.into() + + offset_carry.into() + - ((x_digit.into()) * (y.into())); + + let new_offset_carry = (offset_sum.shr(WORD_BITS)).as_u64(); + let new_x = offset_sum.as_u64(); + offset_carry = new_offset_carry; + a.set(i, new_x); + + i += 1; + }; + + // Return the borrow. + WORD_MAX - offset_carry +} + + +#[cfg(test)] +mod tests { + use alexandria_data_structures::vec::VecTrait; + use alexandria_data_structures::vec::{Felt252Vec, Felt252VecImpl}; + use core::num::traits::{WrappingSub, WrappingMul}; + use core::result::ResultTrait; + use core::traits::Into; + + use crate::crypto::modexp::arith::{ + mod_inv, monsq, monpro, compute_r_mod_n, in_place_shl, in_place_shr, big_wrapping_pow, + big_wrapping_mul, big_sq, borrowing_sub, shifted_carrying_mul + }; + use crate::crypto::modexp::mpnat::{ + MPNat, MPNatTrait, WORD_MAX, DOUBLE_WORD_MAX, BASE, Word, WORD_BYTES + }; + use crate::crypto::modexp::mpnat::{mp_nat_to_u128}; + use crate::felt_vec::{Felt252VecTrait}; + use crate::math::{WrappingBitshift, WrappingExponentiation}; + use crate::traits::bytes::ToBytes; + + // the tests are taken from + // [aurora-engine](https://github.com/aurora-is-near/aurora-engine/blob/1213f2c7c035aa523601fced8f75bef61b4728ab/engine-modexp/src/arith.rs#L401) + + fn check_monsq(x: u128, n: u128) { + let mut a = MPNatTrait::from_big_endian(x.to_be_bytes_padded()); + let mut m = MPNatTrait::from_big_endian(n.to_be_bytes_padded()); + let n_prime = WORD_MAX - mod_inv(m.digits[0]) + 1; + + let mut output = Felt252VecImpl::new(); + output.expand(2 * m.digits.len() + 1).unwrap(); + + monsq(ref a, ref m, n_prime, ref output); + let mut result = MPNat { digits: output }; + + let mut output = Felt252VecImpl::new(); + output.expand(m.digits.len() + 2).unwrap(); + let mut tmp = MPNat { digits: a.digits.duplicate() }; + monpro(ref a, ref tmp, ref m, n_prime, ref output); + + let mut expected = MPNat { digits: output }; + + assert!(result.digits.equal_remove_trailing_zeroes(ref expected.digits)); + } + + fn check_monpro(x: u128, y: u128, n: u128, ref expected: MPNat) { + let mut a = MPNatTrait::from_big_endian(x.to_be_bytes_padded()); + let mut b = MPNatTrait::from_big_endian(y.to_be_bytes_padded()); + let mut m = MPNatTrait::from_big_endian(n.to_be_bytes()); + let n_prime = WORD_MAX - mod_inv(m.digits[0]) + 1; + + let mut output = Felt252VecImpl::new(); + output.expand(m.digits.len() + 2).unwrap(); + monpro(ref a, ref b, ref m, n_prime, ref output); + let mut result = MPNat { digits: output }; + + assert!(result.digits.equal_remove_trailing_zeroes(ref expected.digits)); + } + + + fn check_r_mod_n(n: u128, ref expected: MPNat) { + let mut x = MPNatTrait::from_big_endian(n.to_be_bytes_padded()); + let mut out: Felt252Vec = Felt252VecImpl::new(); + out.expand(x.digits.len()).unwrap(); + compute_r_mod_n(ref x, ref out); + let mut result = MPNat { digits: out }; + assert!(result.digits.equal_remove_trailing_zeroes(ref expected.digits)); + } + + fn check_in_place_shl(n: u128, shift: u32) { + let mut x = MPNatTrait::from_big_endian(n.to_be_bytes_padded()); + in_place_shl(ref x.digits, shift); + let mut result = mp_nat_to_u128(ref x); + + let mask = BASE.wrapping_pow(x.digits.len().into()).wrapping_sub(1); + assert_eq!(result, n.wrapping_shl(shift) & mask); + } + + fn check_in_place_shr(n: u128, shift: u32) { + let mut x = MPNatTrait::from_big_endian(n.to_be_bytes_padded()); + in_place_shr(ref x.digits, shift); + let mut result = mp_nat_to_u128(ref x); + + assert_eq!(result, n.wrapping_shr(shift)); + } + + fn check_mod_inv(n: Word) { + let n_inv = mod_inv(n); + assert_eq!(n.wrapping_mul(n_inv), 1); + } + + fn check_big_wrapping_pow(a: u128, b: u32, expected_bytes: Span) { + let mut x = MPNatTrait::from_big_endian(a.to_be_bytes_padded()); + let mut y = b.to_be_bytes_padded(); + + let mut scratch = Felt252VecImpl::new(); + scratch.expand(1 + (expected_bytes.len() / WORD_BYTES)).unwrap(); + + let mut result = big_wrapping_pow(ref x, y, ref scratch); + + let mut expected = MPNatTrait::from_big_endian(expected_bytes); + assert!(result.digits.equal_remove_trailing_zeroes(ref expected.digits)); + } + + fn check_big_wrapping_mul(a: u128, b: u128, output_digits: usize, ref expected: MPNat) { + let mut x = MPNatTrait::from_big_endian(a.to_be_bytes_padded()); + let mut y = MPNatTrait::from_big_endian(b.to_be_bytes_padded()); + + let mut out = Felt252VecImpl::new(); + out.expand(output_digits).unwrap(); + + big_wrapping_mul(ref x, ref y, ref out); + let mut result = MPNat { digits: out }; + + assert!(result.digits.equal_remove_trailing_zeroes(ref expected.digits)); + } + + fn check_big_sq(a: u128, ref expected: MPNat) { + let mut x = MPNatTrait::from_big_endian(a.to_be_bytes_padded()); + let mut out = Felt252VecImpl::new(); + out.expand(2 * x.digits.len() + 1).unwrap(); + + big_sq(ref x, ref out); + + let mut result = MPNat { digits: out }; + assert!(result.digits.equal_remove_trailing_zeroes(ref expected.digits)); + } + + #[test] + fn test_monsq_alpha() { + let mut x = Felt252VecImpl::new(); + x.push(0xf72fc634c83435bc); + x.push(0xa6b0ce70ac511873); + let mut x = MPNat { digits: x }; + + let mut y = Felt252VecImpl::new(); + y.push(0xf3e77eceb2ecfce5); + y.push(0xc4550871a1cfc67a); + let mut y = MPNat { digits: y }; + + let n_prime = 0xa51080a4eb8b9f13; + let mut scratch = Felt252VecImpl::new(); + scratch.expand(5).unwrap(); + + monsq(ref x, ref y, n_prime, ref scratch); + } + + + #[test] + fn test_monsq_0() { + check_monsq(1, 31); + } + + #[test] + fn test_monsq_1() { + check_monsq(6, 31); + } + + #[test] + fn test_monsq_2() { + // This example is intentionally chosen because 5 * 5 = 25 = 0 mod 25, + // therefore it requires the final subtraction step in the algorithm. + check_monsq(5, 25); + } + + #[test] + fn test_monsq_3() { + check_monsq(0x1FFF_FFFF_FFFF_FFF0, 0x1FFF_FFFF_FFFF_FFF1); + } + + #[test] + fn test_monsq_4() { + check_monsq(0x16FF_221F_CB7D, 0x011E_842B_6BAA_5017_EBF2_8293); + } + + #[test] + fn test_monsq_5() { + check_monsq(0x0A2D_63F5_CFF9, 0x1F3B_3BD9_43EF); + } + + #[test] + fn test_monsq_6() { + check_monsq(0xa6b0ce71a380dea7c83435bc, 0xc4550871a1cfc67af3e77eceb2ecfce5,); + } + + #[test] + fn test_monpro_0() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(2); + let mut expected = MPNat { digits: expected_digits }; + + check_monpro(1, 1, 31, ref expected); + } + + #[test] + fn test_monpro_1() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(22); + let mut expected = MPNat { digits: expected_digits }; + + check_monpro(6, 7, 31, ref expected); + } + + #[test] + fn test_monpro_2() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(0); + let mut expected = MPNat { digits: expected_digits }; + + // This example is intentionally chosen because 5 * 7 = 35 = 0 mod 35, + // therefore it requires the final subtraction step in the algorithm. + check_monpro(5, 7, 35, ref expected); + } + + #[test] + fn test_monpro_3() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(384307168202282284); + + let mut expected = MPNat { digits: expected_digits }; + + // This example is intentionally chosen because 5 * 7 = 35 = 0 mod 35, + // therefore it requires the final subtraction step in the algorithm. + check_monpro(0x1FFF_FFFF_FFFF_FFF0, 0x1234, 0x1FFF_FFFF_FFFF_FFF1, ref expected); + } + + #[test] + fn test_monpro_4() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(0); + let mut expected = MPNat { digits: expected_digits }; + + // This example is intentionally chosen because 5 * 7 = 35 = 0 mod 35, + // therefore it requires the final subtraction step in the algorithm. + check_monpro( + 0x16FF_221F_CB7D, 0x0C75_8535_434F, 0x011E_842B_6BAA_5017_EBF2_8293, ref expected + ); + } + + #[test] + fn test_monpro_5() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(4425093052866); + let mut expected = MPNat { digits: expected_digits }; + + // This example is intentionally chosen because 5 * 7 = 35 = 0 mod 35, + // therefore it requires the final subtraction step in the algorithm. + check_monpro(0x0A2D_63F5_CFF9, 0x1B21_FF3C_FA8E, 0x1F3B_3BD9_43EF, ref expected); + } + + #[test] + fn test_r_mod_n_0() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + let mut expected = MPNat { digits: expected_digits }; + + check_r_mod_n(0x01_00_00_00_01, ref expected); + } + + #[test] + fn test_r_mod_n_1() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(549722259457); + let mut expected = MPNat { digits: expected_digits }; + + check_r_mod_n(0x80_00_00_00_01, ref expected); + } + + #[test] + fn test_r_mod_n_2() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + let mut expected = MPNat { digits: expected_digits }; + + check_r_mod_n(0xFFFF_FFFF_FFFF_FFFF, ref expected); + } + + #[test] + fn test_r_mod_n_3() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + let mut expected = MPNat { digits: expected_digits }; + + check_r_mod_n(0x0001_0000_0000_0000_0001, ref expected); + } + + #[test] + fn test_r_mod_n_4() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(18446181123756130305); + expected_digits.push(32767); + + let mut expected = MPNat { digits: expected_digits }; + + check_r_mod_n(0x8000_0000_0000_0000_0001, ref expected); + } + + #[test] + fn test_r_mod_n_5() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(3491005389787767287); + expected_digits.push(2668502225); + + let mut expected = MPNat { digits: expected_digits }; + + check_r_mod_n(0xbf2d_c9a3_82c5_6e85_b033_7651, ref expected); + } + + #[test] + fn test_r_mod_n_6() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(4294967296); + + let mut expected = MPNat { digits: expected_digits }; + + check_r_mod_n(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF, ref expected); + } + + #[test] + fn test_in_place_shl() { + check_in_place_shl(0, 0); + check_in_place_shl(1, 10); + check_in_place_shl(WORD_MAX.into(), 5); + check_in_place_shl(DOUBLE_WORD_MAX.into(), 16); + } + + #[test] + fn test_in_place_shr() { + check_in_place_shr(0, 0); + check_in_place_shr(1, 10); + check_in_place_shr(0x1234_5678, 10); + check_in_place_shr(WORD_MAX.into(), 5); + check_in_place_shr(DOUBLE_WORD_MAX, 16); + } + + #[test] + #[available_gas(10000000000000)] + fn test_mod_inv_0() { + let mut i = 1; + loop { + if i == 1025 { + break; + }; + + check_mod_inv(2 * i - 1); + i += 1; + } + } + + #[test] + #[available_gas(10000000000000)] + fn test_mod_inv_1() { + let mut i = 0; + loop { + if i == 1025 { + break; + }; + + check_mod_inv(0xFF_FF_FF_FF - 2 * i); + i += 1; + } + } + + #[test] + fn test_big_wrapping_pow_0() { + let expected_bytes: Span = [1].span(); + check_big_wrapping_pow(1, 1, expected_bytes); + } + + #[test] + fn test_big_wrapping_pow_1() { + let expected_bytes: Span = [100].span(); + + check_big_wrapping_pow(10, 2, expected_bytes); + } + + #[test] + fn test_big_wrapping_pow_2() { + let expected_bytes: Span = [1, 0, 0, 0, 0].span(); + + check_big_wrapping_pow(2, 32, expected_bytes); + } + + #[test] + fn test_big_wrapping_pow_3() { + let expected_bytes: Span = [1, 0, 0, 0, 0, 0, 0, 0, 0].span(); + check_big_wrapping_pow(2, 64, expected_bytes); + } + + #[test] + #[available_gas(10000000000000)] + fn test_big_wrapping_pow_4() { + let expected_bytes: Span = [ + 3, + 218, + 116, + 252, + 230, + 21, + 167, + 46, + 59, + 185, + 199, + 194, + 149, + 140, + 133, + 157, + 60, + 102, + 160, + 27, + 104, + 79, + 16, + 104, + 20, + 104, + 116, + 207, + 214, + 100, + 237, + 159, + 0, + 245, + 249, + 156, + 52, + 33, + 217, + 113, + 130, + 6, + 65, + 78, + 49, + 141, + 141, + 160, + 29, + 125, + 168, + 236, + 88, + 174, + 146, + 81, + 137, + 165, + 242, + 90, + 251, + 115, + 144, + 169, + 141, + 66, + 207, + 230, + 56, + 199, + 140, + 109, + 7, + 99, + 35, + 155, + 88, + 29, + 90, + 192, + 55, + 127, + 112, + 26, + 176, + 181, + 13, + 72, + 107, + 209, + 1, + 210, + 88, + 233, + 185, + 87, + 108, + 122, + 168, + 137, + 255, + 36, + 201, + 185, + 31, + 36, + 51, + 208, + 64, + 154, + 113, + 233, + 71, + 95, + 35, + 253, + 0, + 3, + 159, + 183, + 10, + 83, + 233, + 88, + 96, + 19, + 104, + 229, + 132, + 73, + 219, + 152, + 126, + 215, + 249, + 46, + 110, + 157, + 234, + 2, + 100, + 178, + 150, + 110, + 217, + 246, + 128, + 219, + 121, + 21, + 234, + 55, + 101, + 81, + 207, + 191, + 200, + 201, + 2, + 40, + 13, + 80, + 107, + 226, + 143, + 164, + 254, + 91, + 54, + 46, + 254, + 7, + 14, + 136, + 149, + 194, + 6, + 191, + 14, + 49, + 140, + 193, + 40, + 1, + 138, + 165, + 82, + 34, + 33, + 169, + 41, + 136, + 130, + 47, + 84, + 173, + 58, + 121, + 192, + 247, + 98, + 237, + 165, + 215, + 161, + 198, + 87, + 228, + 76, + 160, + 66, + 78, + 169, + 139, + 234, + 169, + 83, + 15, + 16, + 192, + 170, + 71, + 227, + 232, + 116, + 189, + 81, + 64, + 104, + 182, + 129, + 203, + 191, + 210, + 151, + 132, + 254, + 239, + 19, + 138, + 49, + 113, + 140, + 77, + 38, + 49, + 117, + 127, + 203, + 123, + 127, + 49, + 32, + 61, + 108, + 120, + 133, + 119, + 8, + 232, + 84, + 57, + 103, + 197, + 160, + 65, + 191, + 82, + 253, + 60, + 191, + 209, + 63, + 176, + 43, + 33, + 54, + 75, + 17, + 73, + 222, + 198, + 80, + 5, + 14, + 50, + 117, + 156, + 77, + 147, + 190, + 230, + 143, + 47, + 149, + 180, + 203, + 144, + 202, + 102, + 231, + 2, + 91, + 22, + 101, + 178, + 211, + 233, + 109, + 156, + 72, + 151, + 199, + 189, + 90, + 76, + 21, + 112, + 21, + 2, + 44, + 96, + 42, + 141, + 217, + 142, + 23, + 75, + 248, + 209, + 26, + 3, + 198, + 103, + 227, + 103, + 140, + 99, + 75, + 211, + 152, + 109, + 19, + 72, + 6, + 116, + 67, + 70, + 32, + 45, + 5, + 113, + 179, + 252, + 2, + 202, + 115, + 244, + 68, + 128, + 156, + 233, + 227, + 211, + 5, + 146, + 147, + 186, + 34, + 3, + 105, + 147, + 64, + 79, + 172, + 141, + 14, + 60, + 69, + 249, + 169, + 76, + 252, + 84, + 151, + 49, + 81, + 246, + 185, + 181, + 181, + 226, + 28, + 152, + 30, + 47, + 248, + 103, + 21, + 184, + 140, + 193, + 112, + 139, + 250, + 206, + 35, + 180, + 122, + 32, + 151, + 105, + 30, + 193, + 68, + 232, + 170, + 174, + 254, + 143, + 29, + 165, + 194, + 14, + 164, + 35, + 25, + 250, + 86, + 76, + 213, + 159, + 21, + 0, + 212, + 146, + 21, + 8, + 180, + 73, + 250, + 116, + 137, + 221, + 20, + 22, + 146, + 169, + 120, + 166, + 229, + 226, + 136, + 201, + 177, + 49, + 21, + 228, + 191, + 246, + 26, + 36, + 183, + 175, + 137, + 71, + 4, + 46, + 235, + 197, + 99, + 0, + 142, + 97, + 184, + 34, + 84, + 254, + 41, + 95, + 198, + 178, + 48, + 105, + 215, + 72, + 155, + 238, + 51, + 164, + 52, + 179, + 126, + 254, + 100, + 35, + 236, + 63, + 215, + 238, + 217, + 239, + 229, + 160, + 192, + 33, + 82, + 165, + 81, + 149, + 186, + 53, + 109, + 184, + 187, + 186, + 8, + 43, + 249, + 20, + 37, + 255, + 241, + 18, + 61, + 97, + 229, + 29, + 201, + 144, + 92, + 202, + 215, + 161, + 165, + 133, + 89, + 180, + 246, + 37, + 16, + 133, + 226, + 209, + 23, + 61, + 241, + 25, + 9, + 150, + 154, + 150, + 133, + 210, + 62, + 115, + 34, + 201, + 187, + 217, + 3, + 82, + 102, + 174, + 233, + 33, + 31, + 7, + 4, + 88, + 70, + 173, + 157, + 111, + 96, + 102, + 223, + 157, + 224, + 158, + 235, + 191, + 55, + 219, + 218, + 146, + 233, + 242, + 250, + 170, + 100, + 68, + 37, + 56, + 251, + 109, + 112, + 217, + 209, + 46, + 229, + 198, + 198, + 156, + 198, + 70, + 76, + 131, + 79, + 40, + 25, + 176, + 21, + 43, + 31, + 121, + 204, + 225, + 128, + 182, + 191, + 148, + 72, + 22, + 112, + 63, + 223, + 182, + 155, + 177, + 183, + 72, + 111, + 6, + 196, + 250, + 189, + 45, + 97, + 182, + 14, + 219, + 189, + 50, + 226, + 91, + 1, + 86, + 95, + 131, + 120, + 224, + 0, + 71, + 28, + 151, + 69, + 24, + 93, + 82, + 237, + 136, + 103, + 90, + 247, + 173, + 204, + 121, + 199, + 17, + 164, + 80, + 49, + 183, + 10, + 200, + 235, + 56, + 72, + 72, + 147, + 150, + 223, + 110, + 165, + 60, + 13, + 251, + 42, + 193, + 78, + 212, + 166, + 178, + 103, + 19, + 35, + 69, + 10, + 137, + 62, + 13, + 90, + 203, + 126, + 203, + 207, + 190, + 184, + 89, + 118, + 186, + 203, + 6, + 115, + 158, + 168, + 35, + 206, + 227, + 48, + 221, + 252, + 190, + 166, + 249, + 96, + 92, + 244, + 77, + 213, + 119, + 44, + 207, + 17, + 16, + 118, + 104, + 106, + 188, + 205, + 5, + 240, + 14, + 181, + 227, + 4, + 11, + 32, + 91, + 224, + 78, + 175, + 49, + 19, + 12, + 233, + 131, + 141, + 47, + 32, + 14, + 195, + 214, + 77, + 158, + 39, + 114, + 167, + 37, + 16, + 249, + 73, + 167, + 230, + 165, + 19, + 4, + 199, + 227, + 251, + 184, + 131, + 137, + 74, + 176, + 116, + 35, + 182, + 121, + 62, + 114, + 64, + 163, + 84, + 208, + 111, + 56, + 191, + 88, + 130, + 64, + 64, + 181, + 162, + 53, + 34, + 16, + 179, + 155, + 137, + 138, + 101, + 121, + 73, + 234, + 189, + 100, + 141, + 122, + 123, + 79, + 200, + 90, + 208, + 83, + 253, + 124, + 125, + 116, + 72, + 138, + 63, + 42, + 144, + 200, + 73, + 233, + 113, + 143, + 85, + 140, + 16, + 240, + 230, + 42, + 114, + 137, + 193, + 10, + 129, + 124, + 193, + 104, + 177, + 55, + 156, + 173, + 135, + 168, + 217, + 1, + 46, + 41, + 132, + 17, + 222, + 178, + 226, + 24, + 108, + 117, + 199, + 171, + 232, + 129, + 82, + 225, + 214, + 105, + 94, + 188, + 72, + 62, + 91, + 193, + 188, + 18, + 33, + 131, + 18, + 194, + 70, + 151, + 187, + 42, + 5, + 62, + 85, + 38, + 134, + 252, + 183, + 227, + 120, + 19, + 152, + 243, + 235, + 114, + 208, + 78, + 57, + 113, + 217, + 182, + 125, + 195, + 64, + 229, + 232, + 54, + 118, + 11, + 119, + 163, + 235, + 12, + 67, + 90, + 246, + 76, + 219, + 200, + 124, + 234, + 41, + 172, + 31, + 167, + 213, + 127, + 100, + 163, + 72, + 44, + 107, + 171, + 229, + 189, + 68, + 201, + 244, + 154, + 27, + 172, + 228, + 234, + 192, + 156, + 127, + 170, + 9, + 78, + 166, + 249, + 154, + 178, + 179, + 172, + 220, + 205, + 220, + 60, + 86, + 98, + 134, + 60, + 134, + 89, + 244, + 187, + 231, + 128, + 6, + 109, + 152, + 251, + 44, + 208, + 238, + 169, + 71, + 51, + 192, + 242, + 57, + 8, + 62, + 206, + 94, + 94, + 25, + 220, + 160, + 175, + 35, + 113, + 66, + 42, + 134, + 241, + 57, + 253, + 44, + 244, + 163, + 158, + 152, + 147, + 79, + 142, + 190, + 139, + 222, + 202, + 216, + 220, + 47, + 179, + 207, + 199, + 104, + 1, + 21, + 106, + 142, + 188, + 105, + 247, + 111, + 202, + 78, + 145, + 66, + 216, + 222, + 96, + 138, + 133, + 28, + 235, + 204, + 100, + 183, + 232, + 65, + 138, + 196, + 133, + 23, + 154, + 0, + 187, + 252, + 32, + 106, + 76, + 94, + 129, + 173, + 13, + 79, + 167, + 103, + 54, + 51, + 102, + 224, + 231, + 159, + 127, + 54, + 131, + 122, + 65, + 83, + 195, + 9, + 175, + 45, + 179, + 32, + 118, + 230, + 101, + 85, + 13, + 85, + 234, + 26, + 16, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ].span(); + check_big_wrapping_pow(2766, 844, expected_bytes); + } + + #[test] + fn test_big_wrapping_mul_0() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(0); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul(0, 0, 1, ref expected); + } + + + #[test] + fn test_big_wrapping_mul_1() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul(1, 1, 1, ref expected); + } + + #[test] + fn test_big_wrapping_mul_2() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(42); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul(7, 6, 1, ref expected); + } + + #[test] + fn test_big_wrapping_mul_3() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + expected_digits.push(18446744073709551614); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul(WORD_MAX.into(), WORD_MAX.into(), 2, ref expected); + } + + #[test] + fn test_big_wrapping_mul_4() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul(WORD_MAX.into(), WORD_MAX.into(), 1, ref expected); + } + + #[test] + fn test_big_wrapping_mul_5() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(42); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul(DOUBLE_WORD_MAX - 5, DOUBLE_WORD_MAX - 6, 2, ref expected); + } + + #[test] + fn test_big_wrapping_mul_6() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(6727192404480162174); + expected_digits.push(3070707315540124665); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul(0xa945_aa5e_429a_6d1a, 0x4072_d45d_3355_237b, 3, ref expected); + } + + #[test] + fn test_big_wrapping_mul_7() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(2063196614268007784); + expected_digits.push(7048986299143829482); + expected_digits.push(14065833420641261004); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_wrapping_mul( + 0x8ae1_5515_fc92_b1c0_b473_8ce8_6bbf_7218, + 0x43e9_8b77_1f7c_aa93_6c4c_85e9_7fd0_504f, + 3, + ref expected + ); + } + + #[test] + fn test_big_sq_0() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(0); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(0, ref expected); + } + + #[test] + fn test_big_sq_1() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(1, ref expected); + } + + #[test] + fn test_big_sq_2() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + expected_digits.push(18446744073709551614); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(WORD_MAX.into(), ref expected); + } + + #[test] + fn test_big_sq_3() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(4); + expected_digits.push(18446744073709551608); + expected_digits.push(3); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(2 * WORD_MAX.into(), ref expected); + } + + #[test] + fn test_big_sq_4() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(0); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(0, ref expected); + } + + #[test] + fn test_big_sq_5() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(2532367871917050473); + expected_digits.push(16327525306720758713); + expected_digits.push(15087745550001425684); + expected_digits.push(5708046406239628566); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(0x8e67904953db9a2bf6da64bf8bda866d, ref expected); + } + + + #[test] + fn test_big_sq_6() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(15092604974397072849); + expected_digits.push(3791921091882282235); + expected_digits.push(12594445234582458012); + expected_digits.push(7165619740963215273); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(0x9f8dc1c3fc0bf50fe75ac3bbc03124c9, ref expected); + } + + #[test] + fn test_big_sq_7() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(5163998055150312593); + expected_digits.push(8460506958278925118); + expected_digits.push(17089393176389340230); + expected_digits.push(6902937458884066534); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(0x9c9a17378f3d064e5eaa80eeb3850cd7, ref expected); + } + + + #[test] + fn test_big_sq_8() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(2009857620356723108); + expected_digits.push(5657228334642978155); + expected_digits.push(88889113116670247); + expected_digits.push(12075559273075793199); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(0xcf2025cee03025d247ad190e9366d926, ref expected); + } + + #[test] + fn test_big_sq_9() { + let mut expected_digits: Felt252Vec = Felt252VecImpl::new(); + expected_digits.push(1); + expected_digits.push(0); + expected_digits.push(18446744073709551614); + expected_digits.push(18446744073709551615); + + let mut expected = MPNat { digits: expected_digits }; + + check_big_sq(DOUBLE_WORD_MAX, ref expected); + } + + // Test for addition overflows in the big_sq inner loop */ + #[test] + fn test_big_sq_10() { + let mut x = MPNatTrait::from_big_endian( + [ + 0xff, + 0xff, + 0xff, + 0xff, + 0x80, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0x80, + 0x00, + 0x00, + 0x00, + ].span() + ); + + let mut out = Felt252VecImpl::new(); + out.expand(2 * x.digits.len() + 1).unwrap(); + + big_sq(ref x, ref out); + let mut result = MPNat { digits: out }; + + let mut expected = MPNatTrait::from_big_endian( + [ + 0xff, + 0xff, + 0xff, + 0xff, + 0x00, + 0x00, + 0x00, + 0x01, + 0x40, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0xff, + 0xff, + 0xff, + 0xfe, + 0x40, + 0x00, + 0x00, + 0x01, + 0x90, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xbf, + 0xff, + 0xff, + 0xff, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ].span() + ); + + assert!(result.digits.equal_remove_trailing_zeroes(ref expected.digits)); + } + + #[test] + fn test_borrowing_sub() { + assert_eq!(borrowing_sub(0, 0, false), (0, false)); + assert_eq!(borrowing_sub(1, 0, false), (1, false)); + assert_eq!(borrowing_sub(47, 5, false), (42, false)); + assert_eq!(borrowing_sub(101, 7, true), (93, false)); + assert_eq!(borrowing_sub(0x00_00_01_00, 0x00_00_02_00, false), (WORD_MAX - 0xFF, true)); + assert_eq!(borrowing_sub(0x00_00_01_00, 0x00_00_10_00, true), (WORD_MAX - 0x0F_00, true)); + } + + + #[test] + fn test_shifted_carrying_mul() { + assert_eq!(shifted_carrying_mul(0, 0, 0, 0), (0, 0)); + assert_eq!(shifted_carrying_mul(0, 6, 7, 0), (42, 0)); + assert_eq!(shifted_carrying_mul(0, 6, 7, 8), (50, 0)); + assert_eq!(shifted_carrying_mul(5, 6, 7, 8), (55, 0)); + assert_eq!( + shifted_carrying_mul( + WORD_MAX - 0x11, WORD_MAX - 0x1234, WORD_MAX - 0xABCD, WORD_MAX - 0xFF + ), + (0x0C_38_0C_94, WORD_MAX - 0xBE00) + ); + assert_eq!( + shifted_carrying_mul(WORD_MAX, WORD_MAX, WORD_MAX, WORD_MAX), (WORD_MAX, WORD_MAX) + ); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/lib.cairo b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/lib.cairo new file mode 100644 index 000000000..a904bf8af --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/lib.cairo @@ -0,0 +1,19 @@ +// CREDITS: The implementation has been take from +// [aurora-engine](https://github.com/aurora-is-near/aurora-engine/tree/develop/engine-modexp) + +use crate::crypto::modexp::mpnat::MPNatTrait; +use crate::felt_vec::{Felt252VecTrait}; + +/// Computes `(base ^ exp) % modulus`, where all values are given as big-endian +/// encoded bytes. +pub fn modexp(base: Span, exp: Span, modulus: Span) -> Span { + let mut x = MPNatTrait::from_big_endian(base); + let mut m = MPNatTrait::from_big_endian(modulus); + + if m.digits.len == 1 && m.digits[0] == 0 { + return [].span(); + } + + let mut result = x.modpow(exp, ref m); + result.digits.to_be_bytes() +} diff --git a/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/mpnat.cairo b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/mpnat.cairo new file mode 100644 index 000000000..2fdefeca3 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/crypto/modexp/mpnat.cairo @@ -0,0 +1,1033 @@ +// CREDITS: The implementation has been take from +// [aurora-engine](https://github.com/aurora-is-near/aurora-engine/tree/develop/engine-modexp) +use alexandria_data_structures::vec::VecTrait; +use alexandria_data_structures::vec::{Felt252Vec, Felt252VecImpl}; +use core::array::SpanTrait; +use core::num::traits::CheckedMul; +use core::option::OptionTrait; +use core::result::ResultTrait; +use crate::felt_vec::{Felt252VecTrait}; +use crate::math::Bitshift; +use crate::traits::bytes::FromBytes; +use crate::traits::integer::{U64Trait, U128Trait, BitsUsed, ByteSize}; + +use super::arith::{ + big_wrapping_pow, mod_inv, compute_r_mod_n, join_as_double, in_place_shl, in_place_shr, + in_place_add, in_place_mul_sub, big_wrapping_mul, monsq, monpro, borrowing_sub, carrying_add +}; + +pub type Word = u64; +pub type DoubleWord = u128; +pub const WORD_BYTES: usize = 8; +pub const WORD_BITS: usize = 64; +pub const WORD_MAX: Word = 18446744073709551615; +// 2**64 +pub const BASE: DoubleWord = 18446744073709551616; +pub const DOUBLE_WORD_MAX: DoubleWord = 340282366920938463463374607431768211455; +/// Multi-precision natural number, represented in base `Word::MAX + 1 = 2^WORD_BITS`. +/// The digits are stored in little-endian order, i.e. digits[0] is the least +/// significant digit. +#[derive(Destruct)] +pub struct MPNat { + pub digits: Felt252Vec +} + + +#[generate_trait] +pub impl MPNatTraitImpl of MPNatTrait { + fn from_big_endian(bytes: Span) -> MPNat { + if bytes.is_empty() { + let mut digits = Default::default(); + digits.expand(1).unwrap(); + return MPNat { digits }; + } + + // Remainder on division by WORD_BYTES + let r = bytes.len() & (WORD_BYTES - 1); + let n_digits: usize = if r == 0 { + bytes.len() / WORD_BYTES + } else { + // Need an extra digit for the remainder + (bytes.len() / WORD_BYTES) + 1 + }; + + let mut digits: Felt252Vec = Felt252VecImpl::new(); + // safe unwrap, since n_digits >= 0; + digits.expand(n_digits).unwrap(); + + // buffer to hold Word-sized slices of the input bytes + // TODO: optimize, see if we can avoid using this Felt252Vec + let mut buf: Felt252Vec = Felt252VecImpl::new(); + // safe unwrap, since WORD_BYTES > 0 + buf.expand(WORD_BYTES).unwrap(); + + let mut i = n_digits - 1; + if r != 0 { + // safe unwrap, since we know index is in bound + no overflow + buf.copy_from_bytes_le((WORD_BYTES - r), bytes.slice(0, r)).unwrap(); + + // safe unwrap, since we know that bytes won't overflow + let word = buf.to_le_bytes().from_be_bytes().expect('mpnat_from_big_endian_word'); + digits.set(i, word); + + if i == 0 { + // Special case where there is just one digit + return MPNat { digits }; + } + + i -= 1; + }; + + let mut j = r; + loop { + let next_j = j + WORD_BYTES; + // safe unwrap, since we know index is in bound + no overflow + buf.copy_from_bytes_le(0, bytes.slice(j, next_j - j)).unwrap(); + + // safe unwrap, since we know that bytes won't overflow + let word: u64 = buf.to_le_bytes().from_be_bytes().expect('mpnat_from_big_endian_word'); + digits.set(i, word); + + if i == 0 { + break; + } + + i -= 1; + j = next_j; + }; + + digits.remove_trailing_zeroes(); + + if digits.len() == 0 { + digits.expand(1).unwrap(); + } + + MPNat { digits } + } + + /// Makes `self` have the same number of digits as `other` by + /// pushing 0s or dropping higher order digits as needed. + /// This is equivalent to reducing `self` modulo `2^(WORD_BITS*k)` where + /// `k` is the number of digits in `other`. + fn sub_to_same_size(ref self: MPNat, ref other: MPNat) { + self.digits.remove_trailing_zeroes(); + if (self.digits.len() == 0) { + self.digits.expand(1).unwrap(); + } + + let n = other.digits.len(); + let s = self.digits.len(); + let m = if n >= s { + return; + } else { + s - n + }; + + let other_most_sig: DoubleWord = other.digits[other.digits.len() - 1].into(); + + if self.digits.len() == 2 { // This is the smallest case since `n >= 1` and `m > 0` + // implies that `self.digits.len() >= 2`. + // In this case we can use DoubleWord-sized arithmetic + // to get the answer directly. + let self_most_sig = self.digits.pop().unwrap(); + let a = join_as_double(self_most_sig, self.digits[0]); + let b = other_most_sig; + self.digits.set(0, (a % b).as_u64()); + return; + }; + + if n == 1 { + // The divisor is only 1 digit, so the long-division + // algorithm is easy. + let k = self.digits.len() - 1; + let mut i = k; + loop { + if i == 0 { + break; + }; + + i -= 1; + + let self_most_sig = self.digits.pop().unwrap(); + let self_second_sig = self.digits[i]; + let r = join_as_double(self_most_sig, self_second_sig) % other_most_sig; + self.digits.set(i, r.as_u64()); + }; + + return; + } + + // At this stage we know that `n >= 2` and `self.digits.len() >= 3`. + // The smaller cases are covered in the if-statements above. + + // The algorithm below only works well when the divisor's + // most significant digit is at least `BASE / 2`. + // If it is too small then we "normalize" by multiplying + // both numerator and denominator by a common factor + // and run the algorithm on those numbers. + // See Knuth The Art of Computer Programming vol. 2 section 4.3 for details. + let shift: u32 = other_most_sig.as_u64().count_leading_zeroes().into(); + if shift > 0 { + // Normalize self + let overflow = in_place_shl(ref self.digits, shift); + self.digits.push(overflow); + + // Normalize other + let mut normalized = other.digits.duplicate(); + in_place_shl(ref normalized, shift); + + let mut v = MPNat { digits: normalized }; + // Run algorithm on normalized values + self.sub_to_same_size(ref v); + + // need to de-normalize to get the correct result + in_place_shr(ref self.digits, shift); + + return; + }; + + let other_second_sig: DoubleWord = other.digits[n - 2].into(); + let mut self_most_sig: Word = 0; + + let mut j = m + 1; + + loop { + if j == 0 { + break; + } + + j -= 1; + + let self_second_sig = self.digits[self.digits.len() - 1]; + let self_third_sig = self.digits[self.digits.len() - 2]; + + let a = join_as_double(self_most_sig, self_second_sig); + let mut q_hat = a / other_most_sig; + let mut r_hat = a % other_most_sig; + + loop { + let a = q_hat * other_second_sig; + let b = join_as_double(r_hat.as_u64(), self_third_sig); + if q_hat >= BASE || a > b { + q_hat -= 1; + r_hat += other_most_sig; + if BASE <= r_hat { + break; + } + } else { + break; + } + }; + + //TODO: optimize with [#720](https://github.com/kkrt-labs/kakarot-ssj/issues/720) + let mut a = self.digits.clone_slice(j, self.digits.len() - j); + + let mut borrow = in_place_mul_sub(ref a, ref other.digits, q_hat.as_u64()); + self.digits.insert_vec(j, ref a).unwrap(); + if borrow > self_most_sig { + // q_hat was too large, add back one multiple of the modulus + //TODO: optimize with [#720](https://github.com/kkrt-labs/kakarot-ssj/issues/720) + let mut a = self.digits.clone_slice(j, self.digits.len() - j); + in_place_add(ref a, ref other.digits); + self.digits.insert_vec(j, ref a).unwrap(); + borrow -= 1; + } + + self_most_sig = self.digits.pop().unwrap(); + }; + + self.digits.push(self_most_sig); + } + + fn is_power_of_two(ref self: MPNat) -> bool { + // A multi-precision number is a power of 2 iff exactly one digit + // is a power of 2 and all others are zero. + + let mut found_power_of_two = false; + + let mut i = 0; + loop { + if i == self.digits.len() { + break found_power_of_two; + } + + let d = self.digits[i]; + let is_p2 = if d != 0 { + (d & (d - 1)) == 0 + } else { + false + }; + + if ((!is_p2 && d != 0) || (is_p2 && found_power_of_two)) { + break false; + } else if is_p2 { + found_power_of_two = true; + } + + i += 1; + } + } + + fn is_odd(ref self: MPNat) -> bool { + // when the value is 0 + if self.digits.len() == 0 { + return false; + }; + + // A binary number is odd iff its lowest order bit is set. + self.digits[0] & 1 == 1 + } + + // KoΓ§'s algorithm for inversion mod 2^k + // https://eprint.iacr.org/2017/411.pdf + fn koc_2017_inverse(ref aa: MPNat, k: usize) -> MPNat { + let length = k / WORD_BITS; + let mut digits = Felt252VecImpl::new(); + digits.expand(length + 1).unwrap(); + let mut b = MPNat { digits }; + + b.digits.set(0, 1); + + let mut a = MPNat { digits: aa.digits.duplicate(), }; + a.digits.resize(length + 1); + + let mut neg: bool = false; + + let mut digits = Felt252VecImpl::new(); + digits.expand(length + 1).unwrap(); + let mut res = MPNat { digits }; + + let (mut wordpos, mut bitpos) = (0, 0); + + let mut i = 0; + loop { + if i == k { + break; + } + + let x = b.digits[0] & 1; + if x != 0 { + if !neg { + // b = a - b + //TODO: optimize with + //[#720](https://github.com/kkrt-labs/kakarot-ssj/issues/720) + let mut tmp = MPNat { digits: a.digits.duplicate(), }; + in_place_mul_sub(ref tmp.digits, ref b.digits, 1); + b = tmp; + neg = true; + } else { + // b = b - a + in_place_add(ref b.digits, ref a.digits); + } + } + + in_place_shr(ref b.digits, 1); + + res.digits.set(wordpos, res.digits[wordpos] | (x.shl(bitpos))); + + bitpos += 1; + if bitpos == WORD_BITS { + bitpos = 0; + wordpos += 1; + } + + i += 1; + }; + + res + } + + + /// Computes `self ^ exp mod modulus`. `exp` must be given as big-endian bytes. + fn modpow(ref self: MPNat, exp: Span, ref modulus: MPNat) -> MPNat { + // exp must be stripped because it is iterated over in + // big_wrapping_pow and modpow_montgomery, and a large + // zero-padded exp leads to performance issues. + let (exp, exp_is_zero) = Self::strip_leading_zeroes(exp); + + // base^0 is always 1, regardless of base. + // Hence the result is 0 for (base^0) % 1, and 1 + // for every modulus larger than 1. + // + // The case of modulus being 0 should have already been + // handled in modexp(). + if exp_is_zero { + if modulus.digits.len() == 1 && modulus.digits[0] == 1 { + let mut digits = Felt252VecImpl::new(); + digits.expand(1).unwrap(); + + return MPNat { digits }; + } else { + let mut digits = Felt252VecImpl::new(); + digits.push(1); + + return MPNat { digits }; + } + } + + if exp.len() <= (ByteSize::::byte_size()) { + let exp_as_number: usize = exp.from_le_bytes_partial().expect('modpow_exp_as_number'); + + match self.digits.len().checked_mul(exp_as_number) { + Option::Some(max_output_digits) => { + if (modulus.digits.len() > max_output_digits) { + // Special case: modulus is larger than `base ^ exp`, so division is not + // relevant + let mut scratch_space: Felt252Vec = Felt252VecImpl::new(); + scratch_space.expand(max_output_digits).unwrap(); + + return big_wrapping_pow(ref self, exp, ref scratch_space); + } + }, + Option::None => {} + }; + } + + if modulus.is_power_of_two() { // return + return self.modpow_with_power_of_two(exp, ref modulus); + } else if modulus.is_odd() { + return self.modpow_montgomery(exp, ref modulus); + } + + // If the modulus is not a power of two and not an odd number then + // it is a product of some power of two with an odd number. In this + // case we will use the Chinese remainder theorem to get the result. + // See http://www.people.vcu.edu/~jwang3/CMSC691/j34monex.pdf + + let trailing_zeros = modulus.digits.count_leading_zeroes(); + let additional_zero_bits: usize = modulus + .digits[trailing_zeros] + .count_trailing_zeroes() + .into(); + + let mut power_of_two = { + let mut digits = Felt252VecImpl::new(); + digits.expand(trailing_zeros + 1).unwrap(); + let mut tmp = MPNat { digits }; + tmp.digits.set(trailing_zeros, 1_u64.shl(additional_zero_bits)); + tmp + }; + + let power_of_two_mask = power_of_two.digits[power_of_two.digits.len() - 1] - 1; + let mut odd = { + let num_digits = modulus.digits.len() - trailing_zeros; + let mut digits = Felt252VecImpl::new(); + digits.expand(num_digits).unwrap(); + let mut tmp = MPNat { digits }; + if additional_zero_bits > 0 { + tmp.digits.set(0, modulus.digits[trailing_zeros].shr(additional_zero_bits)); + let mut i = 1; + loop { + if i == num_digits { + break; + } + + let d = modulus.digits[trailing_zeros + i]; + tmp + .digits + .set( + i - 1, + tmp.digits[i + - 1] + + (d & power_of_two_mask).shl(WORD_BITS - additional_zero_bits) + ); + tmp.digits.set(i, d.shr(additional_zero_bits)); + + i += 1; + }; + } else { + // TODO: explore if we can avoid this clone and just use a copy to avoid deep + // cloning + let mut slice = modulus + .digits + .clone_slice(trailing_zeros, modulus.digits.len() - trailing_zeros); + tmp.digits.insert_vec(0, ref slice).unwrap(); + } + if tmp.digits.len() > 0 { + loop { + if tmp.digits[tmp.digits.len() - 1] != 0 { + break; + }; + + tmp.digits.pop().unwrap(); + }; + }; + tmp + }; + + let mut base_copy = MPNat { digits: self.digits.duplicate(), }; + let mut x1 = base_copy.modpow_montgomery(exp, ref odd); + let mut x2 = self.modpow_with_power_of_two(exp, ref power_of_two); + + let mut odd_inv = Self::koc_2017_inverse( + ref odd, trailing_zeros * WORD_BITS + additional_zero_bits + ); + + let s = power_of_two.digits.len(); + let mut scratch: Felt252Vec = Felt252VecImpl::new(); + scratch.expand(s).unwrap(); + + let mut diff = { + let mut b = false; + let mut i = 0; + loop { + if i == scratch.len() || i == s { + break; + } + + let (diff, borrow) = borrowing_sub( + x2.digits.get(i).unwrap_or(0), x1.digits.get(i).unwrap_or(0), b, + ); + + scratch.set(i, diff); + b = borrow; + + i += 1; + }; + + MPNat { digits: scratch } + }; + + let mut y = { + let mut out: Felt252Vec = Felt252VecImpl::new(); + out.expand(s).unwrap(); + big_wrapping_mul(ref diff, ref odd_inv, ref out); + + out.set(out.len() - 1, out[out.len() - 1] & power_of_two_mask); + MPNat { digits: out } + }; + + // Reuse allocation for efficiency + let mut digits = diff.digits; + let s = modulus.digits.len(); + digits.reset(); + digits.resize(s); + big_wrapping_mul(ref odd, ref y, ref digits); + let mut c = false; + + let mut i = 0; + loop { + if i == digits.len() { + break; + }; + + let out_digit = digits[i]; + + let (sum, carry) = carrying_add(x1.digits.get(i).unwrap_or(0), out_digit, c); + c = carry; + digits.set(i, sum); + + i += 1; + }; + + MPNat { digits } + } + + // Computes `self ^ exp mod modulus` using Montgomery multiplication. + // See https://www.microsoft.com/en-us/research/wp-content/uploads/1996/01/j37acmon.pdf + fn modpow_montgomery(ref self: MPNat, exp: Span, ref modulus: MPNat) -> MPNat { + // n_prime satisfies `r * (r^(-1)) - modulus * n' = 1`, where + // `r = 2^(WORD_BITS*modulus.digits.len())`. + let n_prime = WORD_MAX - mod_inv(modulus.digits[0]) + 1; + let s = modulus.digits.len; + + let mut digits = Felt252VecImpl::new(); + // safe unwrap, since initial length is 0; + digits.expand(s).unwrap(); + let mut x_bar = MPNat { digits }; + // Initialize result as `r mod modulus` (Montgomery form of 1) + compute_r_mod_n(ref modulus, ref x_bar.digits); + + // Reduce base mod modulus + self.sub_to_same_size(ref modulus); + + // Need to compute a_bar = base * r mod modulus; + // First directly multiply base * r to get a 2s-digit number, + // then reduce mod modulus. + let mut a_bar = { + let mut digits = Felt252VecImpl::new(); + digits.expand(2 * s).unwrap(); + let mut tmp = MPNat { digits }; + big_wrapping_mul(ref self, ref x_bar, ref tmp.digits); + + tmp.sub_to_same_size(ref modulus); + tmp + }; + + // scratch space for monpro algorithm + let mut scratch: Felt252Vec = Felt252VecImpl::new(); + scratch.expand(2 * s + 1).unwrap(); + let monpro_len = s + 2; + + let mut i = 0; + loop { + if i == exp.len() { + break; + } + + let b = *exp[i]; + + let mut mask: u8 = 128; + + loop { + if mask == 0 { + break; + }; + + monsq(ref x_bar, ref modulus, n_prime, ref scratch); + //TODO: optimize + let mut slice = scratch.clone_slice(0, s); + x_bar.digits.copy_from_vec_le(ref slice).unwrap(); + scratch.reset(); + + if b & mask != 0 { + let mut slice = scratch.clone_slice(0, monpro_len); + monpro(ref x_bar, ref a_bar, ref modulus, n_prime, ref slice); + scratch.insert_vec(0, ref slice).unwrap(); + + //TODO: optimize + let mut slice = scratch.clone_slice(0, s); + x_bar.digits.copy_from_vec_le(ref slice).unwrap(); + scratch.reset(); + } + mask = mask.shr(1); + }; + + i += 1; + }; + + // Convert out of Montgomery form by computing monpro with 1 + let mut one = { + // We'll reuse the memory space from a_bar for efficiency. + let mut digits = a_bar.digits; + digits.reset(); + digits.set(0, 1); + MPNat { digits } + }; + + let mut slice = scratch.clone_slice(0, monpro_len); + monpro(ref x_bar, ref one, ref modulus, n_prime, ref slice); + scratch.insert_vec(0, ref slice).unwrap(); + + scratch.resize(s); + MPNat { digits: scratch } + } + + fn modpow_with_power_of_two(ref self: MPNat, exp: Span, ref modulus: MPNat) -> MPNat { + // We know `modulus` is a power of 2. So reducing is as easy as bit shifting. + // We also know the modulus is non-zero because 0 is not a power of 2. + + // First reduce self to be the same size as the modulus + self.force_same_size(ref modulus); + + // The modulus is a power of 2 but that power may not be a multiple of a whole word. + // We can clear out any higher order bits to fix this. + let modulus_mask = modulus.digits[modulus.digits.len() - 1] - 1; + self.digits.set(self.digits.len() - 1, self.digits[self.digits.len() - 1] & modulus_mask); + + // We know that `totient(2^k) = 2^(k-1)`, therefore by Euler's theorem + // we can also reduce the exponent mod `2^(k-1)`. Effectively this means + // throwing away bytes to make `exp` shorter. Note: Euler's theorem only applies + // if the base and modulus are coprime (which in this case means the base is odd). + let exp = if self.is_odd() && (exp.len() > WORD_BYTES * modulus.digits.len()) { + let i = exp.len() - WORD_BYTES * modulus.digits.len(); + exp.slice(i, exp.len() - i) + } else { + exp + }; + + let mut scratch_space = Felt252VecImpl::new(); + // safe unwrap, since the initial length is 0 + scratch_space.expand(modulus.digits.len()).unwrap(); + + let mut result = big_wrapping_pow(ref self, exp, ref scratch_space); + + // The modulus is a power of 2 but that power may not be a multiple of a whole word. + // We can clear out any higher order bits to fix this. + + result + .digits + .set(result.digits.len() - 1, result.digits[result.digits.len() - 1] & modulus_mask); + + result + } + + + /// Makes `self` have the same number of digits as `other` by + /// pushing 0s or dropping higher order digits as needed. + /// This is equivalent to reducing `self` modulo `2^(WORD_BITS*k)` where + /// `k` is the number of digits in `other`. + fn force_same_size(ref self: MPNat, ref other: MPNat) { + self.digits.resize(other.digits.len); + } + + /// stips leading zeroes from little endian bytes + /// # Arguments + /// * `input` a Span in little endian + /// # Returns + /// * (Span<8>, bool), where span is the resulting Span after removing leading zeroes, and the + /// boolean indicates if all bytes were zero + fn strip_leading_zeroes(mut v: Span) -> (Span, bool) { + loop { + let stripped_span = v; + match v.pop_front() { + Option::Some(v) => { if (*v != 0) { + break (stripped_span, false); + } }, + Option::None => { break (v, true); } + } + } + } +} + +pub fn mp_nat_to_u128(ref x: MPNat) -> u128 { + let result = x.digits.to_le_bytes(); + let mut i: usize = 0; + loop { + if i == result.len() { + break; + }; + + i += 1; + }; + result.from_le_bytes_partial().expect('mpnat_to_u128') +} + +#[cfg(test)] +mod tests { + use alexandria_data_structures::vec::Felt252VecImpl; + use alexandria_data_structures::vec::VecTrait; + use crate::crypto::modexp::mpnat::MPNatTrait; + use crate::math::{Bitshift, WrappingBitshift}; + use crate::traits::bytes::ToBytes; + use super::mp_nat_to_u128; + + // the tests are taken from + // [aurora-engine](https://github.com/aurora-is-near/aurora-engine/blob/1213f2c7c035aa523601fced8f75bef61b4728ab/engine-modexp/src/mpnat.rs#L825) + + fn check_modpow_even(base: u128, exp: u128, modulus: u128, expected: u128) { + let mut x = MPNatTrait::from_big_endian(base.to_be_bytes()); + let mut m = MPNatTrait::from_big_endian(modulus.to_be_bytes()); + let mut result = x.modpow(exp.to_be_bytes_padded(), ref m); + let result = mp_nat_to_u128(ref result); + assert_eq!(result, expected); + } + + fn check_modpow_with_power_of_two(base: u128, exp: u128, modulus: u128, expected: u128) { + let mut x = MPNatTrait::from_big_endian(base.to_be_bytes()); + let mut m = MPNatTrait::from_big_endian(modulus.to_be_bytes()); + let mut result = x.modpow_with_power_of_two(exp.to_be_bytes(), ref m); + let result = mp_nat_to_u128(ref result); + assert_eq!(result, expected); + } + + fn check_modpow_montgomery(base: u128, exp: u128, modulus: u128, expected: u128) { + let mut x = MPNatTrait::from_big_endian(base.to_be_bytes()); + let mut m = MPNatTrait::from_big_endian(modulus.to_be_bytes()); + let mut result = x.modpow_montgomery(exp.to_be_bytes(), ref m); + let result = mp_nat_to_u128(ref result); + assert_eq!(result, expected, "({base} ^ {exp}) % {modulus} failed check_modpow_montgomery"); + } + + fn check_sub_to_same_size(a: u128, n: u128) { + let mut x = MPNatTrait::from_big_endian(a.to_be_bytes()); + let mut y = MPNatTrait::from_big_endian(n.to_be_bytes()); + x.sub_to_same_size(ref y); + + assert!(x.digits.len() <= y.digits.len()); + let result = mp_nat_to_u128(ref x); + assert_eq!(result % n, a % n, "{a} % {n} failed sub_to_same_size check"); + } + + + fn check_is_odd(n: u128) { + let mut mp = MPNatTrait::from_big_endian(n.to_be_bytes()); + assert_eq!(mp.is_odd(), n % 2 == 1, "{n} failed is_odd test"); + } + + fn check_is_p2(n: u128, expected_result: bool) { + let mut mp = MPNatTrait::from_big_endian(n.to_be_bytes()); + assert_eq!(mp.is_power_of_two(), expected_result, "{n} failed is_power_of_two test"); + } + + #[test] + #[available_gas(100000000000000)] + fn test_modpow_even() { + check_modpow_even(3, 5, 500, 243); + check_modpow_even(3, 5, 20, 3); + check_modpow_even( + 0x2ff4f4df4c518867207c84b57a77aa50, + 0xca83c2925d17c577c9a03598b6f360, + 0xf863d4f17a5405d84814f54c92f803c8, + 0x8d216c9a1fb275ed18eb340ed43cacc0, + ); + check_modpow_even( + 0x13881e1614244c56d15ac01096b070e7, + 0x336df5b4567cbe4c093271dc151e6c72, + 0x7540f399a0b6c220f1fc60d2451a1ff0, + 0x1251d64c552e8f831f5b841d2811f9c1, + ); + check_modpow_even( + 0x774d5b2494a449d8f22b22ea542d4ddf, + 0xd2f602e1688f271853e7794503c2837e, + 0xa80d20ebf75f92192159197b60f36e8e, + 0x3fbbba42489b27fc271fb39f54aae2e1, + ); + check_modpow_even( + 0x756e409cc3583a6b68ae27ccd9eb3d50, + 0x16dafb38a334288954d038bedbddc970, + 0x1f9b2237f09413d1fc44edf9bd02b8bc, + 0x9347445ac61536a402723cd07a3f5a4, + ); + check_modpow_even( + 0x6dcb8405e2cc4dcebee3e2b14861b47d, + 0xe6c1e5251d6d5deb8dddd0198481d671, + 0xe34a31d814536e8b9ff6cc5300000000, + 0xaa86af638386880334694967564d0c3d, + ); + check_modpow_even( + 0x9c12fe4a1a97d17c1e4573247a43b0e5, + 0x466f3e0a2e8846b8c48ecbf612b96412, + 0x710d7b9d5718acff0000000000000000, + 0x569bf65929e71cd10a553a8623bdfc99, + ); + check_modpow_even( + 0x6d018fdeaa408222cb10ff2c36124dcf, + 0x8e35fc05d490bb138f73c2bc284a67a7, + 0x6c237160750d78400000000000000000, + 0x3fe14e11392c6c6be8efe956c965d5af, + ); + } + + #[test] + fn test_modpow_with_power_of_two() { + check_modpow_with_power_of_two(3, 2, 1.wrapping_shl(30), 9); + check_modpow_with_power_of_two(3, 5, 1.wrapping_shl(30), 243); + check_modpow_with_power_of_two(3, 1_000_000, 1.wrapping_shl(30), 641836289); + check_modpow_with_power_of_two(3, 1_000_000, 1.wrapping_shl(31), 1715578113); + check_modpow_with_power_of_two(3, 1_000_000, 1.wrapping_shl(32), 3863061761); + check_modpow_with_power_of_two( + 0xabcd_ef01_2345_6789_1111, 0x1234_5678_90ab_cdef, 1.wrapping_shl(5), 17, + ); + check_modpow_with_power_of_two( + 0x3f47_9dc0_d5b9_6003, + 0xa180_e045_e314_8581, + 1.wrapping_shl(118), + 0x0028_3d19_e6cc_b8a0_e050_6abb_b9b1_1a03, + ); + } + + #[test] + #[available_gas(100000000000000)] + fn test_modpow_montgomery() { + check_modpow_montgomery(3, 5, 0x9346_9d50_1f74_d1c1, 243); + check_modpow_montgomery(3, 5, 19, 15); + check_modpow_montgomery( + 0x5c4b74ec760dfb021499f5c5e3c69222, + 0x62b2a34b21cf4cc036e880b3fb59fe09, + 0x7b799c4502cd69bde8bb12601ce3ff15, + 0x10c9d9071d0b86d6a59264d2f461200, + ); + check_modpow_montgomery( + 0xadb5ce8589030e3a9112123f4558f69c, + 0xb002827068f05b84a87431a70fb763ab, + 0xc4550871a1cfc67af3e77eceb2ecfce5, + 0x7cb78c0e1c1b43f6412e9d1155ea96d2, + ); + check_modpow_montgomery( + 0x26eb51a5d9bf15a536b6e3c67867b492, + 0xddf007944a79bf55806003220a58cc6, + 0xc96275a80c694a62330872b2690f8773, + 0x23b75090ead913def3a1e0bde863eda7, + ); + check_modpow_montgomery( + 0xb93fa81979e597f548c78f2ecb6800f3, + 0x5fad650044963a271898d644984cb9f0, + 0xbeb60d6bd0439ea39d447214a4f8d3ab, + 0x354e63e6a5e007014acd3e5ea88dc3ad, + ); + check_modpow_montgomery( + 0x1993163e4f578869d04949bc005c878f, + 0x8cb960f846475690259514af46868cf5, + 0x52e104dc72423b534d8e49d878f29e3b, + 0x2aa756846258d5cfa6a3f8b9b181a11c, + ); + } + + #[test] + fn test_sub_to_same_size() { + check_sub_to_same_size(0x10_00_00_00_00, 0xFF_00_00_00); + check_sub_to_same_size(0x10_00_00_00_00, 0x01_00_00_00); + check_sub_to_same_size(0x35_00_00_00_00, 0x01_00_00_00); + check_sub_to_same_size(0xEF_00_00_00_00_00_00, 0x02_FF_FF_FF); + + let n = 10; + let a = 57 + 2 * n + 0x1234_0000_0000 * n + 0x000b_0000_0000_0000_0000 * n; + check_sub_to_same_size(a, n); + + // Test that borrow equals self_most_sig at end of sub_to_same_size */ + { + let mut x = MPNatTrait::from_big_endian( + [ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xae, + 0x5f, + 0xf0, + 0x8b, + 0xfc, + 0x02, + 0x71, + 0xa4, + 0xfe, + 0xe0, + 0x49, + 0x02, + 0xc9, + 0xd9, + 0x12, + 0x61, + 0x8e, + 0xf5, + 0x02, + 0x2c, + 0xa0, + 0x00, + 0x00, + 0x00, + ].span() + ); + let mut y = MPNatTrait::from_big_endian( + [ + 0xae, + 0x5f, + 0xf0, + 0x8b, + 0xfc, + 0x02, + 0x71, + 0xa4, + 0xfe, + 0xe0, + 0x49, + 0x0f, + 0x70, + 0x00, + 0x00, + 0x00, + ].span() + ); + x.sub_to_same_size(ref y); + } + + // Additional test for sub_to_same_size q_hat/r_hat adjustment logic */ + { + let mut x = MPNatTrait::from_big_endian( + [ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ].span() + ); + let mut y = MPNatTrait::from_big_endian( + [ + 0xff, + 0xff, + 0xff, + 0xff, + 0x00, + 0x00, + 0x00, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0x00, + 0x00, + 0x00, + ].span() + ); + x.sub_to_same_size(ref y); + } + } + + #[test] + #[available_gas(100000000000000)] + fn test_mp_nat_is_odd() { + let mut n = 0; + loop { + if n == 1025 { + break; + }; + check_is_odd(n); + + n += 1; + }; + + let mut n = 0xFF_FF_FF_FF_00_00_00_00; + + loop { + if n == 0xFF_FF_FF_FF_00_00_04_01 { + break; + } + + check_is_odd(n); + n += 1; + }; + } + + #[test] + fn test_mp_nat_is_power_of_two() { + check_is_p2(0, false); + check_is_p2(1, true); + check_is_p2(1327, false); + check_is_p2((1.shl(1)) + (1.shl(35)), false); + check_is_p2(1.shl(1), true); + check_is_p2(1.shl(2), true); + check_is_p2(1.shl(3), true); + check_is_p2(1.shl(4), true); + check_is_p2(1.shl(5), true); + check_is_p2(1.shl(31), true); + check_is_p2(1.shl(32), true); + check_is_p2(1.shl(64), true); + check_is_p2(1.shl(65), true); + check_is_p2(1.shl(127), true); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/errors.cairo b/cairo/kakarot-ssj/crates/utils/src/errors.cairo new file mode 100644 index 000000000..24aa9d7ba --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/errors.cairo @@ -0,0 +1,69 @@ +// LENGTH +pub const RLP_EMPTY_INPUT: felt252 = 'KKT: EmptyInput'; +pub const RLP_INPUT_TOO_SHORT: felt252 = 'KKT: InputTooShort'; + +#[derive(Drop, Copy, PartialEq, Debug)] +pub enum RLPError { + EmptyInput, + InputTooShort, + InvalidInput, + Custom: felt252, + NotAString, + FailedParsingU128, + FailedParsingU256, + FailedParsingAddress, + FailedParsingAccessList, + NotAList +} + + +pub impl RLPErrorIntoU256 of Into { + fn into(self: RLPError) -> u256 { + match self { + RLPError::EmptyInput => 'input is null'.into(), + RLPError::InputTooShort => 'input too short'.into(), + RLPError::InvalidInput => 'rlp input not conform'.into(), + RLPError::Custom(msg) => msg.into(), + RLPError::NotAString => 'rlp input is not a string'.into(), + RLPError::FailedParsingU128 => 'rlp failed parsing u128'.into(), + RLPError::FailedParsingU256 => 'rlp failed parsing u256'.into(), + RLPError::FailedParsingAddress => 'rlp failed parsing address'.into(), + RLPError::FailedParsingAccessList => 'rlp failed parsing access_list'.into(), + RLPError::NotAList => 'rlp input is not a list'.into() + } + } +} + +#[generate_trait] +pub impl RLPErrorImpl of RLPErrorTrait { + fn map_err(self: Result) -> Result { + match self { + Result::Ok(val) => Result::Ok(val), + Result::Err(error) => { Result::Err(EthTransactionError::RLPError(error)) } + } + } +} + +#[derive(Drop, Copy, PartialEq, Debug)] +pub enum EthTransactionError { + RLPError: RLPError, + ExpectedRLPItemToBeList, + ExpectedRLPItemToBeString, + TransactionTypeError, + // the usize represents the encountered length of payload + TopLevelRlpListWrongLength: usize, + // the usize represents the encountered length of payload + LegacyTxWrongPayloadLength: usize, + // the usize represents the encountered length of payload + TypedTxWrongPayloadLength: usize, + IncorrectChainId, + IncorrectAccountNonce, + /// If the transaction's fee is less than the base fee of the block + FeeCapTooLow, + /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total + /// fee cap. + TipAboveFeeCap, + /// Thrown to ensure no one is able to specify a transaction with a tip that is too high. + TipVeryHigh, + Other: felt252 +} diff --git a/cairo/kakarot-ssj/crates/utils/src/eth_transaction.cairo b/cairo/kakarot-ssj/crates/utils/src/eth_transaction.cairo new file mode 100644 index 000000000..f10c40ba3 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/eth_transaction.cairo @@ -0,0 +1,68 @@ +pub mod common; +pub mod eip1559; +pub mod eip2930; +pub mod legacy; +pub mod transaction; +pub mod tx_type; +use crate::errors::{EthTransactionError, RLPErrorImpl}; +use crate::traits::bytes::ByteArrayExt; + + +/// Checks the effective gas price of a transaction as specified in EIP-1559 with relevant checks. +/// +/// # Arguments +/// +/// * `max_fee_per_gas` - The maximum fee per gas the user is willing to pay. +/// * `max_priority_fee_per_gas` - The maximum priority fee per gas the user is willing to pay +/// (optional). +/// * `block_base_fee` - The base fee per gas for the current block. +/// +/// # Returns +/// +/// * `Result<(), EthTransactionError>` - Ok if the gas fee is valid, or an error if not. +pub fn check_gas_fee( + max_fee_per_gas: u128, max_priority_fee_per_gas: Option, block_base_fee: u128, +) -> Result<(), EthTransactionError> { + let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(0); + + if max_fee_per_gas < block_base_fee { + // `base_fee_per_gas` is greater than the `max_fee_per_gas` + return Result::Err(EthTransactionError::FeeCapTooLow); + } + if max_fee_per_gas < max_priority_fee_per_gas { + // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` + return Result::Err(EthTransactionError::TipAboveFeeCap); + } + + Result::Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::errors::EthTransactionError; + use super::check_gas_fee; + + #[test] + fn test_happy_path() { + let result = check_gas_fee(100, Option::Some(10), 50); + assert!(result.is_ok()); + } + + #[test] + fn test_fee_cap_too_low() { + let result = check_gas_fee(40, Option::Some(10), 50); + assert_eq!(result, Result::Err(EthTransactionError::FeeCapTooLow)); + } + + #[test] + fn test_tip_above_fee_cap() { + let result = check_gas_fee(100, Option::Some(110), 50); + assert_eq!(result, Result::Err(EthTransactionError::TipAboveFeeCap)); + } + + #[test] + fn test_priority_fee_none() { + let result = check_gas_fee(100, Option::None, 50); + assert!(result.is_ok()); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/eth_transaction/common.cairo b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/common.cairo new file mode 100644 index 000000000..92b012a3b --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/common.cairo @@ -0,0 +1,51 @@ +use core::starknet::EthAddress; + +/// The `to` field of a transaction. Either a target address, or empty for a +/// contract creation. +#[derive(Copy, Drop, Debug, Default, PartialEq, Serde)] +pub enum TxKind { + /// A transaction that creates a contract. + #[default] + Create, + /// A transaction that calls a contract or transfer. + Call: EthAddress, +} + +impl OptionAddressIntoTxKind of Into, TxKind> { + fn into(self: Option) -> TxKind { + match self { + Option::None => TxKind::Create, + Option::Some(address) => TxKind::Call(address), + } + } +} + +impl AddressIntoTxKind of Into { + fn into(self: EthAddress) -> TxKind { + TxKind::Call(self) + } +} + +#[generate_trait] +pub impl TxKindImpl of TxKindTrait { + fn is_create(self: @TxKind) -> bool { + match self { + TxKind::Create => true, + TxKind::Call(_) => false, + } + } + + fn is_call(self: @TxKind) -> bool { + match self { + TxKind::Create => false, + TxKind::Call(_) => true, + } + } + + fn to(self: @TxKind) -> Option { + match self { + TxKind::Create => Option::None, + TxKind::Call(address) => Option::Some(*address), + } + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/eth_transaction/eip1559.cairo b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/eip1559.cairo new file mode 100644 index 000000000..eda69f7e8 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/eip1559.cairo @@ -0,0 +1,144 @@ +use core::num::traits::SaturatingSub; +use crate::errors::{EthTransactionError, RLPError, RLPErrorTrait}; +use crate::eth_transaction::common::TxKind; +use crate::eth_transaction::eip2930::AccessListItem; +use crate::rlp::{RLPItem, RLPHelpersTrait}; +use crate::traits::SpanDefault; + +/// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)). +#[derive(Copy, Drop, Debug, Default, PartialEq, Serde)] +pub struct TxEip1559 { + /// EIP-155: Simple replay attack protection + pub chain_id: u64, + /// A scalar value equal to the number of transactions sent by the sender + pub nonce: u64, + /// A scalar value equal to the maximum + /// amount of gas that should be used in executing + /// this transaction. This is paid up-front, before any + /// computation is done and may not be increased + /// later; + pub gas_limit: u64, + /// A scalar value equal to the maximum + /// amount of gas that should be used in executing + /// this transaction. This is paid up-front, before any + /// computation is done and may not be increased + /// later; + /// + /// As ethereum circulation is around 120mil eth as of 2022 that is around + /// 120000000000000000000000000 wei we are safe to use u128 as its max number is: + /// 340282366920938463463374607431768211455 + /// + pub max_fee_per_gas: u128, + /// Max Priority fee that transaction is paying + /// + /// As ethereum circulation is around 120mil eth as of 2022 that is around + /// 120000000000000000000000000 wei we are safe to use u128 as its max number is: + /// 340282366920938463463374607431768211455 + /// + pub max_priority_fee_per_gas: u128, + /// The 160-bit address of the message call’s recipient or, for a contract creation + /// transaction, βˆ… + pub to: TxKind, + /// A scalar value equal to the number of Wei to + /// be transferred to the message call’s recipient or, + /// in the case of contract creation, as an endowment + /// to the newly created account; + pub value: u256, + /// The accessList specifies a list of addresses and storage keys; + /// these addresses and storage keys are added into the `accessed_addresses` + /// and `accessed_storage_keys` global sets (introduced in EIP-2929). + /// A gas cost is charged, though at a discount relative to the cost of + /// accessing outside the list. + pub access_list: Span, + /// Input has two uses depending if transaction is Create or Call (if `to` field is None or + /// Some). pub init: An unlimited size byte array specifying the + /// EVM-code for the account initialisation procedure CREATE, + /// data: An unlimited size byte array specifying the + /// input data of the message call, formally Td. + pub input: Span, +} + +#[generate_trait] +pub impl _impl of TxEip1559Trait { + /// Returns the effective gas price for the given `base_fee`. + /// + /// # Arguments + /// + /// * `base_fee` - The current network base fee, if available + /// + /// # Returns + /// + /// The effective gas price as a u128 + fn effective_gas_price(self: @TxEip1559, base_fee: Option) -> u128 { + match base_fee { + Option::Some(base_fee) => { + let tip = (*self.max_fee_per_gas).saturating_sub(base_fee); + if tip > (*self.max_priority_fee_per_gas) { + (*self.max_priority_fee_per_gas) + base_fee + } else { + *self.max_fee_per_gas + } + }, + Option::None => { *self.max_fee_per_gas } + } + } + + /// Decodes the RLP-encoded fields into a TxEip1559 struct. + /// + /// # Arguments + /// + /// * `data` - A span of RLPItems containing the encoded transaction fields + /// + /// # Returns + /// + /// A Result containing either the decoded TxEip1559 struct or an EthTransactionError + fn decode_fields(ref data: Span) -> Result { + let boxed_fields = data + .multi_pop_front::<9>() + .ok_or(EthTransactionError::RLPError(RLPError::InputTooShort))?; + let [ + chain_id_encoded, + nonce_encoded, + max_priority_fee_per_gas_encoded, + max_fee_per_gas_encoded, + gas_limit_encoded, + to_encoded, + value_encoded, + input_encoded, + access_list_encoded + ] = + (*boxed_fields) + .unbox(); + + let chain_id = chain_id_encoded.parse_u64_from_string().map_err()?; + let nonce = nonce_encoded.parse_u64_from_string().map_err()?; + let max_priority_fee_per_gas = max_priority_fee_per_gas_encoded + .parse_u128_from_string() + .map_err()?; + let max_fee_per_gas = max_fee_per_gas_encoded.parse_u128_from_string().map_err()?; + let gas_limit = gas_limit_encoded.parse_u64_from_string().map_err()?; + let to = to_encoded.try_parse_address_from_string().map_err()?; + let value = value_encoded.parse_u256_from_string().map_err()?; + let input = input_encoded.parse_bytes_from_string().map_err()?; + let access_list = access_list_encoded.parse_access_list().map_err()?; + + let txkind_to = match to { + Option::Some(to) => { TxKind::Call(to) }, + Option::None => { TxKind::Create } + }; + + Result::Ok( + TxEip1559 { + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + to: txkind_to, + value, + access_list, + input, + } + ) + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/eth_transaction/eip2930.cairo b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/eip2930.cairo new file mode 100644 index 000000000..d59ed6be3 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/eip2930.cairo @@ -0,0 +1,128 @@ +use core::starknet::EthAddress; +use crate::errors::{EthTransactionError, RLPError, RLPErrorTrait}; +use crate::eth_transaction::common::TxKind; +use crate::rlp::{RLPItem, RLPHelpersTrait}; +use crate::traits::SpanDefault; + + +#[derive(Copy, Drop, Serde, PartialEq, Debug)] +pub struct AccessListItem { + pub ethereum_address: EthAddress, + pub storage_keys: Span +} + +#[generate_trait] +pub impl AccessListItemImpl of AccessListItemTrait { + fn to_storage_keys(self: @AccessListItem) -> Span<(EthAddress, u256)> { + let AccessListItem { ethereum_address, mut storage_keys } = *self; + + let mut storage_keys_arr = array![]; + for storage_key in storage_keys { + storage_keys_arr.append((ethereum_address, *storage_key)); + }; + + storage_keys_arr.span() + } +} + + +/// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)). +#[derive(Copy, Drop, Debug, Default, PartialEq, Serde)] +pub struct TxEip2930 { + /// Added as EIP-pub 155: Simple replay attack protection + pub chain_id: u64, + /// A scalar value equal to the number of transactions sent by the sender; formally Tn. + pub nonce: u64, + /// A scalar value equal to the number of + /// Wei to be paid per unit of gas for all computation + /// costs incurred as a result of the execution of this transaction; formally Tp. + /// + /// As ethereum circulation is around 120mil eth as of 2022 that is around + /// 120000000000000000000000000 wei we are safe to use u128 as its max number is: + /// 340282366920938463463374607431768211455 + pub gas_price: u128, + /// A scalar value equal to the maximum + /// amount of gas that should be used in executing + /// this transaction. This is paid up-front, before any + /// computation is done and may not be increased + /// later; formally Tg. + pub gas_limit: u64, + /// The 160-bit address of the message call’s recipient or, for a contract creation + /// transaction, βˆ…, used here to denote the only member of B0 ; formally Tt. + pub to: TxKind, + /// A scalar value equal to the number of Wei to + /// be transferred to the message call’s recipient or, + /// in the case of contract creation, as an endowment + /// to the newly created account; + pub value: u256, + /// The accessList specifies a list of addresses and storage keys; + /// these addresses and storage keys are added into the `accessed_addresses` + /// and `accessed_storage_keys` global sets (introduced in EIP-2929). + /// A gas cost is charged, though at a discount relative to the cost of + /// accessing outside the list. + pub access_list: Span, + /// Input has two uses depending if transaction is Create or Call (if `to` field is None or + /// Some). pub init: An unlimited size byte array specifying the + /// EVM-code for the account initialisation procedure CREATE, + /// data: An unlimited size byte array specifying the + /// input data of the message call; + pub input: Span, +} + + +#[generate_trait] +pub impl _impl of TxEip2930Trait { + /// Decodes the RLP-encoded fields into a TxEip2930 struct. + /// + /// # Arguments + /// + /// * `data` - A span of RLPItems containing the encoded transaction fields + /// + /// # Returns + /// + /// A Result containing either the decoded TxEip2930 struct or an EthTransactionError + fn decode_fields(ref data: Span) -> Result { + let boxed_fields = data + .multi_pop_front::<8>() + .ok_or(EthTransactionError::RLPError(RLPError::InputTooShort))?; + let [ + chain_id_encoded, + nonce_encoded, + gas_price_encoded, + gas_limit_encoded, + to_encoded, + value_encoded, + input_encoded, + access_list_encoded + ] = + (*boxed_fields) + .unbox(); + + let chain_id = chain_id_encoded.parse_u64_from_string().map_err()?; + let nonce = nonce_encoded.parse_u64_from_string().map_err()?; + let gas_price = gas_price_encoded.parse_u128_from_string().map_err()?; + let gas_limit = gas_limit_encoded.parse_u64_from_string().map_err()?; + let to = to_encoded.try_parse_address_from_string().map_err()?; + let value = value_encoded.parse_u256_from_string().map_err()?; + let input = input_encoded.parse_bytes_from_string().map_err()?; + let access_list = access_list_encoded.parse_access_list().map_err()?; + + let txkind_to = match to { + Option::Some(to) => { TxKind::Call(to) }, + Option::None => { TxKind::Create } + }; + + Result::Ok( + TxEip2930 { + chain_id: chain_id, + nonce, + gas_price, + gas_limit, + input, + access_list, + to: txkind_to, + value, + } + ) + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/eth_transaction/legacy.cairo b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/legacy.cairo new file mode 100644 index 000000000..541a1821b --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/legacy.cairo @@ -0,0 +1,96 @@ +use crate::errors::{RLPError, EthTransactionError, RLPErrorTrait}; +use crate::eth_transaction::common::TxKind; +use crate::rlp::{RLPItem, RLPHelpersTrait}; +use crate::traits::SpanDefault; +use crate::traits::{DefaultSignature}; + + +#[derive(Copy, Drop, Debug, Default, PartialEq, Serde)] +pub struct TxLegacy { + /// Added as EIP-155: Simple replay attack protection + pub chain_id: Option, + /// A scalar value equal to the number of transactions sent by the sender; formally Tn. + pub nonce: u64, + /// A scalar value equal to the number of + /// Wei to be paid per unit of gas for all computation + /// costs incurred as a result of the execution of this transaction; formally Tp. + /// + /// As ethereum circulation is around 120mil eth as of 2022 that is around + /// 120000000000000000000000000 wei we are safe to use u128 as its max number is: + /// 340282366920938463463374607431768211455 + pub gas_price: u128, + /// A scalar value equal to the maximum + /// amount of gas that should be used in executing + /// this transaction. This is paid up-front, before any + /// computation is done and may not be increased + /// later; + pub gas_limit: u64, + /// The 160-bit address of the message call’s recipient or, for a contract creation + /// transaction, βˆ…. + pub to: TxKind, + /// A scalar value equal to the number of Wei to + /// be transferred to the message call’s recipient or, + /// in the case of contract creation, as an endowment + /// to the newly created account; + pub value: u256, + /// Input has two uses depending if transaction is Create or Call (if `to` field is None or + /// Some). pub init: An unlimited size byte array specifying the + /// EVM-code for the account initialisation procedure CREATE, + /// data: An unlimited size byte array specifying the + /// input data of the message call. + pub input: Span, +} + +#[generate_trait] +pub impl _impl of TxLegacyTrait { + /// Decodes the RLP-encoded fields into a TxLegacy struct. + /// + /// # Arguments + /// + /// * `data` - A span of RLPItems containing the encoded transaction fields + /// + /// # Returns + /// + /// A Result containing either the decoded TxLegacy struct or an EthTransactionError + fn decode_fields(ref data: Span) -> Result { + let boxed_fields = data + .multi_pop_front::<7>() + .ok_or(EthTransactionError::RLPError(RLPError::InputTooShort))?; + let [ + nonce_encoded, + gas_price_encoded, + gas_limit_encoded, + to_encoded, + value_encoded, + input_encoded, + chain_id_encoded + ] = + (*boxed_fields) + .unbox(); + + let nonce = nonce_encoded.parse_u64_from_string().map_err()?; + let gas_price = gas_price_encoded.parse_u128_from_string().map_err()?; + let gas_limit = gas_limit_encoded.parse_u64_from_string().map_err()?; + let to = to_encoded.try_parse_address_from_string().map_err()?; + let value = value_encoded.parse_u256_from_string().map_err()?; + let input = input_encoded.parse_bytes_from_string().map_err()?; + let chain_id = chain_id_encoded.parse_u64_from_string().map_err()?; + + let transact_to = match to { + Option::Some(to) => { TxKind::Call(to) }, + Option::None => { TxKind::Create } + }; + + Result::Ok( + TxLegacy { + nonce, + gas_price, + gas_limit, + to: transact_to, + value, + input, + chain_id: Option::Some(chain_id), + } + ) + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/eth_transaction/transaction.cairo b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/transaction.cairo new file mode 100644 index 000000000..959ab463b --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/transaction.cairo @@ -0,0 +1,473 @@ +use core::starknet::EthAddress; +use crate::errors::{RLPError, EthTransactionError, RLPErrorTrait}; +use crate::eth_transaction::common::{TxKind, TxKindTrait}; +use crate::eth_transaction::eip1559::{TxEip1559, TxEip1559Trait}; +use crate::eth_transaction::eip2930::{AccessListItem, TxEip2930, TxEip2930Trait}; +use crate::eth_transaction::legacy::{TxLegacy, TxLegacyTrait}; +use crate::eth_transaction::tx_type::{TxType}; +use crate::rlp::{RLPItem, RLPTrait}; +use crate::traits::bytes::U8SpanExTrait; +use crate::traits::{DefaultSignature}; + + +#[derive(Copy, Debug, Drop, PartialEq, Serde)] +pub enum Transaction { + /// Legacy transaction (type `0x0`). + /// + /// Traditional Ethereum transactions, containing parameters `nonce`, `gasPrice`, `gasLimit`, + /// `to`, `value`, `data`, `v`, `r`, and `s`. + /// + /// These transactions do not utilize access lists nor do they incorporate EIP-1559 fee market + /// changes. + #[default] + Legacy: TxLegacy, + /// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)), + /// type `0x1`. + /// + /// The `accessList` specifies an array of addresses and storage keys that the transaction + /// plans to access, enabling gas savings on cross-contract calls by pre-declaring the accessed + /// contract and storage slots. + Eip2930: TxEip2930, + /// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)), + /// type `0x2`. + /// + /// Unlike traditional transactions, EIP-1559 transactions use an in-protocol, dynamically + /// changing base fee per gas, adjusted at each block to manage network congestion. + /// + /// - `maxPriorityFeePerGas`, specifying the maximum fee above the base fee the sender is + /// willing to pay + /// - `maxFeePerGas`, setting the maximum total fee the sender is willing to pay. + /// + /// The base fee is burned, while the priority fee is paid to the miner who includes the + /// transaction, incentivizing miners to include transactions with higher priority fees per + /// gas. + Eip1559: TxEip1559, +} + +#[generate_trait] +pub impl _Transasction of TransactionTrait { + /// Get `chain_id`. + fn chain_id(self: @Transaction) -> Option { + match (*self) { + Transaction::Legacy(tx) => tx.chain_id, + Transaction::Eip2930(TxEip2930 { chain_id, .. }) | + Transaction::Eip1559(TxEip1559 { chain_id, .. }) => Option::Some(chain_id), + } + } + + /// Gets the transaction's [`TxKind`], which is the address of the recipient or + /// [`TxKind::Create`] if the transaction is a contract creation. + fn kind(self: @Transaction) -> TxKind { + match (*self) { + Transaction::Legacy(TxLegacy { to, .. }) | Transaction::Eip2930(TxEip2930 { to, .. }) | + Transaction::Eip1559(TxEip1559 { to, .. }) => to, + } + } + + /// Get the transaction's address of the contract that will be called, or the address that will + /// receive the transfer. + /// + /// Returns `None` if this is a `CREATE` transaction. + fn to(self: @Transaction) -> Option { + self.kind().to() + } + + /// Get the transaction's type + fn transaction_type(self: @Transaction) -> TxType { + match (*self) { + Transaction::Legacy(_) => TxType::Legacy, + Transaction::Eip2930(_) => TxType::Eip2930, + Transaction::Eip1559(_) => TxType::Eip1559, + } + } + + /// Gets the transaction's value field. + fn value(self: @Transaction) -> u256 { + match (*self) { + Transaction::Legacy(TxLegacy { value, .. }) | + Transaction::Eip2930(TxEip2930 { value, .. }) | + Transaction::Eip1559(TxEip1559 { value, .. }) => value, + } + } + + /// Get the transaction's nonce. + fn nonce(self: @Transaction) -> u64 { + match (*self) { + Transaction::Legacy(TxLegacy { nonce, .. }) | + Transaction::Eip2930(TxEip2930 { nonce, .. }) | + Transaction::Eip1559(TxEip1559 { nonce, .. }) => nonce, + } + } + + /// Returns the [`AccessList`] of the transaction. + /// + /// Returns `None` for legacy transactions. + fn access_list(self: @Transaction) -> Option> { + match (*self) { + Transaction::Eip2930(TxEip2930 { access_list, .. }) | + Transaction::Eip1559(TxEip1559 { access_list, .. }) => Option::Some(access_list), + _ => Option::None, + } + } + + /// Get the gas limit of the transaction. + fn gas_limit(self: @Transaction) -> u64 { + match (*self) { + Transaction::Legacy(TxLegacy { gas_limit, .. }) | + Transaction::Eip2930(TxEip2930 { gas_limit, .. }) | + Transaction::Eip1559(TxEip1559 { gas_limit, .. }) => gas_limit.try_into().unwrap(), + } + } + + /// Max fee per gas for eip1559 transaction, for legacy transactions this is `gas_price`. + fn max_fee_per_gas(self: @Transaction) -> u128 { + match (*self) { + Transaction::Eip1559(TxEip1559 { max_fee_per_gas, .. }) => max_fee_per_gas, + Transaction::Legacy(TxLegacy { gas_price, .. }) | + Transaction::Eip2930(TxEip2930 { gas_price, .. }) => gas_price, + } + } + + /// Max priority fee per gas for eip1559 transaction, for legacy and eip2930 transactions this + /// is `None` + fn max_priority_fee_per_gas(self: @Transaction) -> Option { + match (*self) { + Transaction::Eip1559(TxEip1559 { max_priority_fee_per_gas, + .. }) => Option::Some(max_priority_fee_per_gas), + _ => Option::None, + } + } + + /// Return the max priority fee per gas if the transaction is an EIP-1559 transaction, and + /// otherwise return the gas price. + /// + /// # Warning + /// + /// This is different than the `max_priority_fee_per_gas` method, which returns `None` for + /// non-EIP-1559 transactions. + fn priority_fee_or_price(self: @Transaction) -> u128 { + match (*self) { + Transaction::Eip1559(TxEip1559 { max_priority_fee_per_gas, + .. }) => max_priority_fee_per_gas, + Transaction::Legacy(TxLegacy { gas_price, .. }) | + Transaction::Eip2930(TxEip2930 { gas_price, .. }) => gas_price, + } + } + + /// Returns the effective gas price for the given base fee. + /// + /// If the transaction is a legacy or EIP2930 transaction, the gas price is returned. + fn effective_gas_price(self: @Transaction, base_fee: Option) -> u128 { + match (*self) { + Transaction::Legacy(tx) => tx.gas_price, + Transaction::Eip2930(tx) => tx.gas_price, + Transaction::Eip1559(tx) => tx.effective_gas_price(base_fee) + } + } + + /// Get the transaction's input field. + fn input(self: @Transaction) -> Span { + match (*self) { + Transaction::Legacy(tx) => tx.input, + Transaction::Eip2930(tx) => tx.input, + Transaction::Eip1559(tx) => tx.input, + } + } + + + /// Decodes the "raw" format of transaction (similar to `eth_sendRawTransaction`). + /// + /// This should be used for any method that accepts a raw transaction. + /// * `eth_send_raw_transaction`. + /// + /// A raw transaction is either a legacy transaction or EIP-2718 typed transaction. + /// + /// For legacy transactions, the format is encoded as: `rlp(tx-data)`. This format will start + /// with a RLP list header. + /// + /// For EIP-2718 typed transactions, the format is encoded as the type of the transaction + /// followed by the rlp of the transaction: `type || rlp(tx-data)`. + /// + /// Both for legacy and EIP-2718 transactions, an error will be returned if there is an excess + /// of bytes in input data. + fn decode_enveloped(ref tx_data: Span,) -> Result { + if tx_data.is_empty() { + return Result::Err(EthTransactionError::RLPError(RLPError::InputTooShort)); + } + + // Check if it's a list + let transaction_signed = if Self::is_legacy_tx(tx_data) { + // Decode as a legacy transaction + Self::decode_legacy_tx(ref tx_data)? + } else { + Self::decode_enveloped_typed_transaction(ref tx_data)? + }; + + //TODO: check that the entire input was consumed and that there are no extra bytes at the + //end. + + Result::Ok(transaction_signed) + } + + /// Decode a legacy Ethereum transaction + /// This function decodes a legacy Ethereum transaction in accordance with EIP-155. + /// It returns transaction details including nonce, gas price, gas limit, destination address, + /// amount, payload, message hash, chain id. The transaction hash is computed by keccak hashing + /// the signed transaction data, which includes the chain ID in accordance with EIP-155. + /// # Arguments + /// * encoded_tx_data - The raw rlp encoded transaction data + /// * encoded_tx_data - is of the format: rlp![nonce, gasPrice, gasLimit, to , value, data, + /// chainId, 0, 0] + /// Note: this function assumes that tx_type has been checked to make sure it is a legacy + /// transaction + fn decode_legacy_tx(ref encoded_tx_data: Span) -> Result { + let rlp_decoded_data = RLPTrait::decode(encoded_tx_data); + let mut rlp_decoded_data = rlp_decoded_data.map_err()?; + + if (rlp_decoded_data.len() != 1) { + return Result::Err( + EthTransactionError::TopLevelRlpListWrongLength(rlp_decoded_data.len()) + ); + } + + let rpl_item = *rlp_decoded_data.at(0); + let legacy_tx: TxLegacy = match rpl_item { + RLPItem::String => { Result::Err(EthTransactionError::ExpectedRLPItemToBeList)? }, + RLPItem::List(mut val) => { + if (val.len() != 9) { + return Result::Err(EthTransactionError::LegacyTxWrongPayloadLength(val.len())); + } + TxLegacyTrait::decode_fields(ref val)? + } + }; + + Result::Ok(Transaction::Legacy(legacy_tx)) + } + + /// Decodes an enveloped EIP-2718 typed transaction. + /// + /// This should _only_ be used internally in general transaction decoding methods, + /// which have already ensured that the input is a typed transaction with the following format: + /// `tx-type || rlp(tx-data)` + /// + /// Note that this format does not start with any RLP header, and instead starts with a single + /// byte indicating the transaction type. + /// + /// CAUTION: this expects that `data` is `tx-type || rlp(tx-data)` + fn decode_enveloped_typed_transaction( + ref encoded_tx_data: Span + ) -> Result { + // keep this around so we can use it to calculate the hash + let original_data = encoded_tx_data; + + let tx_type = encoded_tx_data + .pop_front() + .ok_or(EthTransactionError::RLPError(RLPError::InputTooShort))?; + let tx_type: TxType = (*tx_type) + .try_into() + .ok_or(EthTransactionError::RLPError(RLPError::Custom('unsupported tx type')))?; + + let rlp_decoded_data = RLPTrait::decode(encoded_tx_data).map_err()?; + if (rlp_decoded_data.len() != 1) { + return Result::Err( + EthTransactionError::RLPError(RLPError::Custom('not encoded as list')) + ); + } + + let mut rlp_decoded_data = match *rlp_decoded_data.at(0) { + RLPItem::String => { + return Result::Err( + EthTransactionError::RLPError(RLPError::Custom('not encoded as list')) + ); + }, + RLPItem::List(v) => { v } + }; + + let transaction = match tx_type { + TxType::Eip2930 => Transaction::Eip2930( + TxEip2930Trait::decode_fields(ref rlp_decoded_data)? + ), + TxType::Eip1559 => Transaction::Eip1559( + TxEip1559Trait::decode_fields(ref rlp_decoded_data)? + ), + TxType::Legacy => { + return Result::Err( + EthTransactionError::RLPError(RLPError::Custom('unexpected legacy tx type')) + ); + } + }; + + Result::Ok(transaction) + } + + /// Returns the hash of the unsigned transaction + /// + /// The hash is used to recover the sender address when verifying the signature + /// attached to the transaction + #[inline(always)] + fn compute_hash(encoded_tx_data: Span) -> u256 { + encoded_tx_data.compute_keccak256_hash() + } + + /// Check if a raw transaction is a legacy Ethereum transaction + /// This function checks if a raw transaction is a legacy Ethereum transaction by checking the + /// transaction type according to EIP-2718. + /// # Arguments + /// * `encoded_tx_data` - The raw rlp encoded transaction data + #[inline(always)] + fn is_legacy_tx(encoded_tx_data: Span) -> bool { + // From EIP2718: if it starts with a value in the range [0xc0, 0xfe] then it is a legacy + // transaction type + if (*encoded_tx_data[0] > 0xbf && *encoded_tx_data[0] < 0xff) { + return true; + } + + return false; + } +} + +#[cfg(test)] +mod tests { + use crate::eth_transaction::common::TxKind; + use crate::eth_transaction::eip2930::AccessListItem; + use crate::eth_transaction::tx_type::TxType; + use crate::test_data::{ + legacy_rlp_encoded_tx, legacy_rlp_encoded_deploy_tx, eip_2930_encoded_tx, + eip_1559_encoded_tx + }; + use super::{TransactionTrait}; + + + #[test] + fn test_decode_legacy_tx() { + // tx_format (EIP-155, unsigned): [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, + // 0] + // expected rlp decoding: [ "0x", "0x3b9aca00", "0x1e8480", + // "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "0x016345785d8a0000", "0xabcdef", + // "0x4b4b5254", "0x", "0x" ] + // message_hash: 0xcf71743e6e25fef715398915997f782b95554c8bbfb7b3f7701e007332ed31b4 + // chain id used: 'KKRT' + let mut encoded_tx_data = legacy_rlp_encoded_tx(); + let transaction = TransactionTrait::decode_enveloped(ref encoded_tx_data).unwrap(); + assert_eq!(transaction.nonce(), 0); + assert_eq!(transaction.max_fee_per_gas(), 0x3b9aca00); + assert_eq!(transaction.gas_limit(), 0x1e8480); + assert_eq!( + transaction.kind(), + TxKind::Call(0x1f9840a85d5af5bf1d1762f925bdaddc4201f984.try_into().unwrap()) + ); + assert_eq!(transaction.value(), 0x016345785d8a0000); + assert_eq!(transaction.input(), [0xab, 0xcd, 0xef].span()); + assert_eq!(transaction.chain_id(), Option::Some(0x4b4b5254)); + assert_eq!(transaction.transaction_type(), TxType::Legacy); + } + + #[test] + fn test_decode_deploy_tx() { + // tx_format (EIP-155, unsigned): [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, + // 0] + // expected rlp decoding: + // ["0x","0x0a","0x061a80","0x","0x0186a0","0x600160010a5060006000f3","0x4b4b5254","0x","0x"] + let mut encoded_tx_data = legacy_rlp_encoded_deploy_tx(); + let transaction = TransactionTrait::decode_enveloped(ref encoded_tx_data).unwrap(); + assert_eq!(transaction.nonce(), 0); + assert_eq!(transaction.max_fee_per_gas(), 0x0a); + assert_eq!(transaction.gas_limit(), 0x061a80); + assert_eq!(transaction.kind(), TxKind::Create); + assert_eq!(transaction.value(), 0x0186a0); + assert_eq!( + transaction.input(), + [0x60, 0x01, 0x60, 0x01, 0x0a, 0x50, 0x60, 0x00, 0x60, 0x00, 0xf3].span() + ); + assert_eq!(transaction.chain_id(), Option::Some(0x4b4b5254)); + assert_eq!(transaction.transaction_type(), TxType::Legacy); + } + + #[test] + fn test_decode_eip2930_tx() { + // tx_format (EIP-2930, unsigned): 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, + // value, data, accessList]) + // expected rlp decoding: [ "0x4b4b5254", "0x", "0x3b9aca00", "0x1e8480", + // "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "0x016345785d8a0000", "0xabcdef", + // [["0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + // ["0xde9fbe35790b85c23f42b7430c78f122636750cc217a534c80a9a0520969fa65", + // "0xd5362e94136f76bfc8dad0b510b94561af7a387f1a9d0d45e777c11962e5bd94"]]] ] + // message_hash: 0xc00f61dcc99a78934275c404267b9d035cad7f71cf3ae2ed2c5a55b601a5c107 + // chain id used: 'KKRT' + + let mut encoded_tx_data = eip_2930_encoded_tx(); + let transaction = TransactionTrait::decode_enveloped(ref encoded_tx_data).unwrap(); + assert_eq!(transaction.chain_id(), Option::Some(0x4b4b5254)); + assert_eq!(transaction.nonce(), 0); + assert_eq!(transaction.max_fee_per_gas(), 0x3b9aca00); + assert_eq!(transaction.gas_limit(), 0x1e8480); + assert_eq!( + transaction.kind(), + TxKind::Call(0x1f9840a85d5af5bf1d1762f925bdaddc4201f984.try_into().unwrap()) + ); + assert_eq!(transaction.value(), 0x016345785d8a0000); + assert_eq!(transaction.input(), [0xab, 0xcd, 0xef].span()); + assert_eq!(transaction.transaction_type(), TxType::Eip2930); + } + + #[test] + fn test_decode_eip1559_tx() { + // tx_format (EIP-1559, unsigned): 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, + // max_fee_per_gas, gas_limit, destination, amount, data, access_list]) + // expected rlp decoding: [ "0x4b4b5254", "0x", "0x", "0x3b9aca00", "0x1e8480", + // "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "0x016345785d8a0000", "0xabcdef", + // [[["0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + // ["0xde9fbe35790b85c23f42b7430c78f122636750cc217a534c80a9a0520969fa65", + // "0xd5362e94136f76bfc8dad0b510b94561af7a387f1a9d0d45e777c11962e5bd94"]]] ] ] + // message_hash: 0xa2de478d0c94b4be637523b818d03b6a1841fca63fd044976fcdbef3c57a87b0 + // chain id used: 'KKRT' + + let mut encoded_tx_data = eip_1559_encoded_tx(); + let transaction = TransactionTrait::decode_enveloped(ref encoded_tx_data).unwrap(); + assert_eq!(transaction.chain_id(), Option::Some(0x4b4b5254)); + assert_eq!(transaction.nonce(), 0); + assert_eq!(transaction.max_fee_per_gas(), 0x3b9aca00); + assert_eq!(transaction.gas_limit(), 0x1e8480); + assert_eq!( + transaction.kind(), + TxKind::Call(0x1f9840a85d5af5bf1d1762f925bdaddc4201f984.try_into().unwrap()) + ); + assert_eq!(transaction.value(), 0x016345785d8a0000); + assert_eq!(transaction.input(), [0xab, 0xcd, 0xef].span()); + let expected_access_list = [ + AccessListItem { + ethereum_address: 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984.try_into().unwrap(), + storage_keys: [ + 0xde9fbe35790b85c23f42b7430c78f122636750cc217a534c80a9a0520969fa65, + 0xd5362e94136f76bfc8dad0b510b94561af7a387f1a9d0d45e777c11962e5bd94 + ].span() + } + ].span(); + assert_eq!(transaction.access_list().expect('access_list is none'), expected_access_list); + assert_eq!(transaction.transaction_type(), TxType::Eip1559); + } + + #[test] + fn test_is_legacy_tx_eip_155_tx() { + let encoded_tx_data = legacy_rlp_encoded_tx(); + let result = TransactionTrait::is_legacy_tx(encoded_tx_data); + + assert(result, 'is_legacy_tx expected true'); + } + + #[test] + fn test_is_legacy_tx_eip_1559_tx() { + let encoded_tx_data = eip_1559_encoded_tx(); + let result = TransactionTrait::is_legacy_tx(encoded_tx_data); + + assert(!result, 'is_legacy_tx expected false'); + } + + #[test] + fn test_is_legacy_tx_eip_2930_tx() { + let encoded_tx_data = eip_2930_encoded_tx(); + let result = TransactionTrait::is_legacy_tx(encoded_tx_data); + + assert(!result, 'is_legacy_tx expected false'); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/eth_transaction/tx_type.cairo b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/tx_type.cairo new file mode 100644 index 000000000..3acaf3b72 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/eth_transaction/tx_type.cairo @@ -0,0 +1,23 @@ +/// Transaction Type +#[derive(Copy, Drop, Debug, PartialEq)] +pub enum TxType { + /// Legacy transaction pre EIP-2929 + #[default] + Legacy, + /// AccessList transaction + Eip2930, + /// Transaction with Priority fee + Eip1559, +} + + +impl _TryInto of TryInto { + fn try_into(self: u8) -> Option { + match self { + 0 => Option::Some(TxType::Legacy), + 1 => Option::Some(TxType::Eip2930), + 2 => Option::Some(TxType::Eip1559), + _ => Option::None, + } + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/felt_vec.cairo b/cairo/kakarot-ssj/crates/utils/src/felt_vec.cairo new file mode 100644 index 000000000..cff02ceb0 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/felt_vec.cairo @@ -0,0 +1,785 @@ +use alexandria_data_structures::vec::VecTrait; +use alexandria_data_structures::vec::{Felt252Vec, Felt252VecImpl}; +use core::num::traits::Zero; +use crate::math::Exponentiation; +use crate::traits::bytes::{ToBytes}; + + +#[derive(Drop, Debug, PartialEq)] +pub enum Felt252VecTraitErrors { + IndexOutOfBound, + Overflow, + LengthIsNotSame, + SizeLessThanCurrentLength +} + +#[generate_trait] +pub impl Felt252VecTraitImpl< + T, + +Drop, + +Copy, + +Felt252DictValue, + +Zero, + +Add, + +Sub, + +Div, + +Mul, + +Exponentiation, + +ToBytes, + +PartialOrd, + +Into, + +PartialEq, +> of Felt252VecTrait { + /// Returns Felt252Vec as a Span<8>, the returned Span is in big endian format + /// # Arguments + /// * `self` a ref Felt252Vec + /// # Returns + /// * A Span representing bytes conversion of `self` in big endian format + fn to_be_bytes(ref self: Felt252Vec) -> Span { + let mut res: Array = array![]; + self.remove_trailing_zeroes(); + + let mut i = self.len(); + + while i != 0 { + i -= 1; + res.append_span(self[i].to_be_bytes_padded()); + }; + + res.span() + } + + /// Returns Felt252Vec as a Span<8>, the returned Span is in little endian format + /// # Arguments + /// * `self` a ref Felt252Vec + /// # Returns + /// * A Span representing bytes conversion of `self` in little endian format + fn to_le_bytes(ref self: Felt252Vec) -> Span { + let mut res: Array = array![]; + + for i in 0 + ..self + .len() { + if self[i] == Zero::zero() { + res.append(Zero::zero()); + } else { + res.append_span(self[i].to_le_bytes()); + } + }; + + res.span() + } + + /// Expands a Felt252Vec to a new length by appending zeroes + /// + /// This function will mutate the Felt252Vec in-place and will expand its length, + /// since the default value for Felt252Dict item is 0, all new elements will be set to 0. + /// If the new length is less than the current length, it will return an error. + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `new_length` the new length of the Felt252Vec + /// + /// # Returns + /// * Result::<(), Felt252VecTraitErrors> + /// + /// # Errors + /// * Felt252VecTraitErrors::SizeLessThanCurrentLength if the new length is less than the + /// current length + fn expand(ref self: Felt252Vec, new_length: usize) -> Result<(), Felt252VecTraitErrors> { + if (new_length < self.len) { + return Result::Err(Felt252VecTraitErrors::SizeLessThanCurrentLength); + }; + + self.len = new_length; + + Result::Ok(()) + } + + /// Sets all elements of the Felt252Vec to zero, mutates the Felt252Vec in-place + /// + /// # Arguments + /// self a ref Felt252Vec + fn reset(ref self: Felt252Vec) { + let mut new_vec: Felt252Vec = Default::default(); + new_vec.len = self.len; + self = new_vec; + } + + /// Returns the leading zeroes of a Felt252Vec + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// + /// # Returns + /// * The number of leading zeroes in `self`. + fn count_leading_zeroes(ref self: Felt252Vec) -> usize { + let mut i = 0; + while i != self.len() && self[i] == Zero::zero() { + i += 1; + }; + + i + } + + /// Resizes the Felt252Vec in-place so that len is equal to new_len. + /// + /// This function will mutate the Felt252Vec in-place and will resize its length to the new + /// length. + /// If new_len is greater than len, the Vec is extended by the difference, with each additional + /// slot filled with 0. If new_len is less than len, the Vec is simply truncated from the right. + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `new_len` the new length of the Felt252Vec + fn resize(ref self: Felt252Vec, new_len: usize) { + self.len = new_len; + } + + + /// Copies the elements from a Span into the Felt252Vec in little endian format, in case + /// of overflow or index being out of bounds, an error is returned + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `index` the index at `self` to start copying from + /// * `slice` a Span + /// + /// # Errors + /// * Felt252VecTraitErrors::IndexOutOfBound if the index is out of bounds + /// * Felt252VecTraitErrors::Overflow if the Span is too big to fit in the Felt252Vec + fn copy_from_bytes_le( + ref self: Felt252Vec, index: usize, mut slice: Span + ) -> Result<(), Felt252VecTraitErrors> { + if (index >= self.len) { + return Result::Err(Felt252VecTraitErrors::IndexOutOfBound); + } + + if ((slice.len() + index) > self.len()) { + return Result::Err(Felt252VecTraitErrors::Overflow); + } + + let mut i = index; + for val in slice { + // safe unwrap, as in case of none, we will never reach this branch + self.set(i, (*val).into()); + i += 1; + }; + + Result::Ok(()) + } + + /// Copies the elements from a Felt252Vec into the Felt252Vec in little endian format, If + /// length of both Felt252Vecs are not same, it will return an error + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `vec` a ref Felt252Vec + /// + /// # Errors + /// * Felt252VecTraitErrors::LengthIsNotSame if the length of both Felt252Vecs are not same + fn copy_from_vec_le( + ref self: Felt252Vec, ref vec: Felt252Vec + ) -> Result<(), Felt252VecTraitErrors> { + if (vec.len() != self.len) { + return Result::Err(Felt252VecTraitErrors::LengthIsNotSame); + } + + self = vec.duplicate(); + + Result::Ok(()) + } + + /// Insert elements of Felt252Vec into another Felt252Vec at a given index, in case of overflow + /// or index being out of bounds, an error is returned + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `idx` the index at `self` to start inserting from + /// * `vec` a ref Felt252Vec + /// + /// # Errors + /// * Felt252VecTraitErrors::IndexOutOfBound if the index is out of bounds + /// * Felt252VecTraitErrors::Overflow if the Felt252Vec is too big to fit in the Felt252Vec + fn insert_vec( + ref self: Felt252Vec, idx: usize, ref vec: Felt252Vec + ) -> Result<(), Felt252VecTraitErrors> { + if idx >= self.len() { + return Result::Err(Felt252VecTraitErrors::IndexOutOfBound); + } + + if (idx + vec.len > self.len) { + return Result::Err(Felt252VecTraitErrors::Overflow); + } + + let stop = idx + vec.len(); + for i in idx..stop { + self.set(i, vec[i - idx]); + }; + + Result::Ok(()) + } + + + /// Removes trailing zeroes from a Felt252Vec + /// + /// # Arguments + /// * `input` a ref Felt252Vec + fn remove_trailing_zeroes(ref self: Felt252Vec) { + let mut new_len = self.len; + while (new_len != 0) && (self[new_len - 1] == Zero::zero()) { + new_len -= 1; + }; + + self.len = new_len; + } + + /// Pops an element out of the vector, returns Option::None if the vector is empty + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// # Returns + /// + /// * Option::Some(T), returns the last element or Option::None if the vector is empty + fn pop(ref self: Felt252Vec) -> Option { + if (self.len) == 0 { + return Option::None; + } + + let popped_ele = self[self.len() - 1]; + self.len = self.len - 1; + Option::Some(popped_ele) + } + + /// takes a Felt252Vec and returns a new Felt252Vec with the same elements + /// + /// Note: this is an expensive operation, as it will create a new Felt252Vec + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// + /// # Returns + /// * A new Felt252Vec with the same elements + fn duplicate(ref self: Felt252Vec) -> Felt252Vec { + let mut new_vec = Default::default(); + + for i in 0..self.len { + new_vec.push(self[i]); + }; + + new_vec + } + + /// Returns a new Felt252Vec with elements starting from `idx` to `idx + len` + /// + /// This function will start cloning from `idx` and will clone `len` elements, it will firstly + /// clone the elements and then return a new Felt252Vec + /// In case of overflow return Option::None + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `idx` the index to start cloning from + /// * `len` the length of the clone + /// + /// # Returns + /// * Felt252Vec + /// + /// # Panics + /// * If the index is out of bounds + /// + /// Note: this is an expensive operation, as it will create a new Felt252Vec + fn clone_slice(ref self: Felt252Vec, idx: usize, len: usize) -> Felt252Vec { + let mut new_vec = Default::default(); + + for i in 0..len { + new_vec.push(self[idx + i]); + }; + + new_vec + } + + /// Returns whether two Felt252Vec are equal after removing trailing_zeroes + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `rhs` a ref Felt252Vec + /// + /// # Returns + /// * bool, returns true if both Felt252Vecs are equal, false otherwise + /// TODO: if this utils is only used for testing, then refactor as a test util + fn equal_remove_trailing_zeroes(ref self: Felt252Vec, ref rhs: Felt252Vec) -> bool { + let mut lhs = self.duplicate(); + lhs.remove_trailing_zeroes(); + + let mut rhs = rhs.duplicate(); + rhs.remove_trailing_zeroes(); + + if lhs.len() != rhs.len() { + return false; + }; + + let mut result = true; + for i in 0..lhs.len() { + if lhs[i] != rhs[i] { + result = false; + break; + } + }; + result + } + + /// Fills a Felt252Vec with a given `value` starting from `start_idx` to `start_idx + len` + /// In case of index out of bounds or overflow, error is returned + /// + /// # Arguments + /// * `self` a ref Felt252Vec + /// * `start_idx` the index to start filling from + /// * `len` the length of the fill + /// * `value` the value to fill the Felt252Vec with + /// + /// # Returns + /// * Result::<(), Felt252VecTraitErrors> + /// + /// # Errors + /// * Felt252VecTraitErrors::IndexOutOfBound if the index is out of bounds + /// * Felt252VecTraitErrors::Overflow if the Felt252Vec is too big to fit in the Felt252Vec + fn fill( + ref self: Felt252Vec, start_idx: usize, len: usize, value: T + ) -> Result<(), Felt252VecTraitErrors> { + // Index out of bounds + if (start_idx >= self.len()) { + return Result::Err(Felt252VecTraitErrors::IndexOutOfBound); + } + + // Overflow + if (start_idx + len > self.len()) { + return Result::Err(Felt252VecTraitErrors::Overflow); + } + + for i in start_idx..start_idx + len { + self.set(i, value); + }; + + Result::Ok(()) + } +} + +#[cfg(test)] +mod tests { + mod felt252_vec_u8_test { + use alexandria_data_structures::vec::{VecTrait, Felt252Vec}; + use crate::felt_vec::{Felt252VecTrait}; + + #[test] + fn test_felt252_vec_u8_to_bytes() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + vec.push(2); + vec.push(3); + + let result = vec.to_le_bytes(); + let expected = [0, 1, 2, 3].span(); + + assert_eq!(result, expected); + } + } + + mod felt252_vec_u64_test { + use alexandria_data_structures::vec::{VecTrait, Felt252Vec}; + use crate::felt_vec::{Felt252VecTrait}; + + #[test] + fn test_felt252_vec_u64_words64_to_le_bytes() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + vec.push(2); + vec.push(3); + + let result = vec.to_le_bytes(); + let expected = [0, 1, 2, 3].span(); + + assert_eq!(result, expected); + } + + #[test] + fn test_felt252_vec_u64_words64_to_be_bytes() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + vec.push(2); + vec.push(3); + + let result = vec.to_be_bytes(); + let expected = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ].span(); + + assert_eq!(result, expected); + } + } + + mod felt252_vec_test { + use alexandria_data_structures::vec::{VecTrait, Felt252Vec}; + use crate::felt_vec::{Felt252VecTrait, Felt252VecTraitErrors}; + + #[test] + fn test_felt252_vec_expand() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + + vec.expand(4).unwrap(); + + assert_eq!(vec.len(), 4); + assert_eq!(vec.pop().unwrap(), 0); + assert_eq!(vec.pop().unwrap(), 0); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 0); + } + + #[test] + fn test_felt252_vec_expand_fail() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + + let result = vec.expand(1); + assert_eq!(result, Result::Err(Felt252VecTraitErrors::SizeLessThanCurrentLength)); + } + + #[test] + fn test_felt252_vec_reset() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + + vec.reset(); + + assert_eq!(vec.len(), 2); + assert_eq!(vec.pop().unwrap(), 0); + assert_eq!(vec.pop().unwrap(), 0); + } + + #[test] + fn test_felt252_vec_count_leading_zeroes() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(0); + vec.push(0); + vec.push(1); + + let result = vec.count_leading_zeroes(); + + assert_eq!(result, 3); + } + + + #[test] + fn test_felt252_vec_resize_len_greater_than_current_len() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + + vec.expand(4).unwrap(); + + assert_eq!(vec.len(), 4); + assert_eq!(vec.pop().unwrap(), 0); + assert_eq!(vec.pop().unwrap(), 0); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 0); + } + + #[test] + fn test_felt252_vec_resize_len_less_than_current_len() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + vec.push(0); + vec.push(0); + + vec.resize(2); + + assert_eq!(vec.len(), 2); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 0); + } + + #[test] + fn test_felt252_vec_len_0() { + let mut vec: Felt252Vec = Default::default(); + vec.push(0); + vec.push(1); + + vec.resize(0); + + assert_eq!(vec.len(), 0); + } + + #[test] + fn test_copy_from_bytes_le_size_equal_to_vec_size() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + let bytes = [1, 2, 3, 4].span(); + vec.copy_from_bytes_le(0, bytes).unwrap(); + + assert_eq!(vec.len(), 4); + assert_eq!(vec.pop().unwrap(), 4); + assert_eq!(vec.pop().unwrap(), 3); + assert_eq!(vec.pop().unwrap(), 2); + assert_eq!(vec.pop().unwrap(), 1); + } + + #[test] + fn test_copy_from_bytes_le_size_less_than_vec_size() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + let bytes = [1, 2].span(); + vec.copy_from_bytes_le(2, bytes).unwrap(); + + assert_eq!(vec.len(), 4); + assert_eq!(vec.pop().unwrap(), 2); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 0); + assert_eq!(vec.pop().unwrap(), 0); + } + + #[test] + fn test_copy_from_bytes_le_size_greater_than_vec_size() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + let bytes = [1, 2, 3, 4].span(); + let result = vec.copy_from_bytes_le(2, bytes); + + assert_eq!(result, Result::Err(Felt252VecTraitErrors::Overflow)); + } + + #[test] + fn test_copy_from_bytes_index_out_of_bound() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + let bytes = [1, 2].span(); + let result = vec.copy_from_bytes_le(4, bytes); + + assert_eq!(result, Result::Err(Felt252VecTraitErrors::IndexOutOfBound)); + } + + #[test] + fn test_copy_from_vec_le() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(2).unwrap(); + + let mut vec2: Felt252Vec = Default::default(); + vec2.push(1); + vec2.push(2); + + vec.copy_from_vec_le(ref vec2).unwrap(); + + assert_eq!(vec.len, 2); + assert_eq!(vec.pop().unwrap(), 2); + assert_eq!(vec.pop().unwrap(), 1); + } + + #[test] + fn test_copy_from_vec_le_not_equal_lengths() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(2).unwrap(); + + let mut vec2: Felt252Vec = Default::default(); + vec2.push(1); + + let result = vec.copy_from_vec_le(ref vec2); + + assert_eq!(result, Result::Err(Felt252VecTraitErrors::LengthIsNotSame)); + } + + + #[test] + fn test_insert_vec_size_equal_to_vec_size() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(2).unwrap(); + + let mut vec2: Felt252Vec = Default::default(); + vec2.push(1); + vec2.push(2); + + vec.insert_vec(0, ref vec2).unwrap(); + + assert_eq!(vec.len(), 2); + assert_eq!(vec.pop().unwrap(), 2); + assert_eq!(vec.pop().unwrap(), 1); + } + + #[test] + fn test_insert_vec_size_less_than_vec_size() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + let mut vec2: Felt252Vec = Default::default(); + vec2.push(1); + vec2.push(2); + + vec.insert_vec(2, ref vec2).unwrap(); + + assert_eq!(vec.len(), 4); + assert_eq!(vec.pop().unwrap(), 2); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 0); + assert_eq!(vec.pop().unwrap(), 0); + } + + #[test] + fn test_insert_vec_size_greater_than_vec_size() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(2).unwrap(); + + let mut vec2: Felt252Vec = Default::default(); + vec2.push(1); + vec2.push(2); + vec2.push(3); + vec2.push(4); + + let result = vec.insert_vec(1, ref vec2); + assert_eq!(result, Result::Err(Felt252VecTraitErrors::Overflow)); + } + + #[test] + fn test_insert_vec_index_out_of_bound() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + let mut vec2: Felt252Vec = Default::default(); + vec2.push(1); + vec2.push(2); + + let result = vec.insert_vec(4, ref vec2); + assert_eq!(result, Result::Err(Felt252VecTraitErrors::IndexOutOfBound)); + } + + #[test] + fn test_remove_trailing_zeroes_le() { + let mut vec: Felt252Vec = Default::default(); + vec.push(1); + vec.push(2); + vec.push(0); + vec.push(0); + + vec.remove_trailing_zeroes(); + + assert_eq!(vec.len(), 2); + assert_eq!(vec.pop().unwrap(), 2); + assert_eq!(vec.pop().unwrap(), 1); + } + + #[test] + fn test_pop() { + let mut vec: Felt252Vec = Default::default(); + vec.push(1); + vec.push(2); + + assert_eq!(vec.pop().unwrap(), 2); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop(), Option::::None); + } + + #[test] + fn test_duplicate() { + let mut vec: Felt252Vec = Default::default(); + vec.push(1); + vec.push(2); + + let mut vec2 = vec.duplicate(); + + assert_eq!(vec.len(), vec2.len()); + assert_eq!(vec.pop(), vec2.pop()); + assert_eq!(vec.pop(), vec2.pop()); + assert_eq!(vec.pop().is_none(), true); + assert_eq!(vec2.pop().is_none(), true); + } + + #[test] + fn test_clone_slice() { + let mut vec: Felt252Vec = Default::default(); + vec.push(1); + vec.push(2); + + let mut vec2 = vec.clone_slice(1, 1); + + assert_eq!(vec2.len(), 1); + assert_eq!(vec2.pop().unwrap(), 2); + } + + #[test] + fn test_equal() { + let mut vec: Felt252Vec = Default::default(); + vec.push(1); + vec.push(2); + + let mut vec2: Felt252Vec = Default::default(); + vec2.push(1); + vec2.push(2); + + assert!(vec.equal_remove_trailing_zeroes(ref vec2)); + vec2.pop().unwrap(); + assert!(!vec.equal_remove_trailing_zeroes(ref vec2)); + } + + #[test] + fn test_fill() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + vec.fill(1, 3, 1).unwrap(); + + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 1); + assert_eq!(vec.pop().unwrap(), 0); + } + + #[test] + fn test_fill_overflow() { + let mut vec: Felt252Vec = Default::default(); + vec.expand(4).unwrap(); + + assert_eq!(vec.fill(4, 0, 1), Result::Err(Felt252VecTraitErrors::IndexOutOfBound)); + assert_eq!(vec.fill(2, 4, 1), Result::Err(Felt252VecTraitErrors::Overflow)); + } + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/fmt.cairo b/cairo/kakarot-ssj/crates/utils/src/fmt.cairo new file mode 100644 index 000000000..d343b31cb --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/fmt.cairo @@ -0,0 +1,50 @@ +use core::fmt::{Debug, Formatter, Error}; +use crate::set::{SpanSet, SpanSetTrait}; + +mod display_felt252_based { + use core::fmt::{Display, Formatter, Error}; + use core::to_byte_array::AppendFormattedToByteArray; + pub impl TDisplay, +Copy> of Display { + fn fmt(self: @T, ref f: Formatter) -> Result<(), Error> { + let value: felt252 = (*self).into(); + let base: felt252 = 10_u8.into(); + value.append_formatted_to_byte_array(ref f.buffer, base.try_into().unwrap()); + Result::Ok(()) + } + } +} + +mod debug_display_based { + use core::fmt::{Display, Debug, Formatter, Error}; + pub impl TDisplay> of Debug { + fn fmt(self: @T, ref f: Formatter) -> Result<(), Error> { + Display::fmt(self, ref f) + } + } +} + +pub impl TSpanSetDebug, +Copy, +Drop> of Debug> { + fn fmt(self: @SpanSet, ref f: Formatter) -> Result<(), Error> { + // For a reason I don't understand, the following code doesn't compile: + // Debug::fmt(@(*self.to_span())sc, ref f) + let mut self = (*self).to_span(); + write!(f, "[")?; + loop { + match self.pop_front() { + Option::Some(value) => { + if Debug::fmt(value, ref f).is_err() { + break Result::Err(Error {}); + }; + if self.is_empty() { + break Result::Ok(()); + } + if write!(f, ", ").is_err() { + break Result::Err(Error {}); + }; + }, + Option::None => { break Result::Ok(()); } + }; + }?; + write!(f, "]") + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/helpers.cairo b/cairo/kakarot-ssj/crates/utils/src/helpers.cairo new file mode 100644 index 000000000..0c84cb933 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/helpers.cairo @@ -0,0 +1,279 @@ +use core::array::ArrayTrait; +use core::array::SpanTrait; +use core::cmp::min; +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::num::traits::SaturatingAdd; + +use core::panic_with_felt252; +use core::pedersen::PedersenTrait; +use core::starknet::{EthAddress, ContractAddress, ClassHash}; +use core::traits::TryInto; +use crate::constants::{CONTRACT_ADDRESS_PREFIX, MAX_ADDRESS}; +use crate::constants::{POW_2, POW_256_1, POW_256_REV}; +use crate::traits::array::{ArrayExtTrait}; +use crate::traits::{U256TryIntoContractAddress, EthAddressIntoU256, BoolIntoNumeric}; + +/// Splits a u128 into two u64 parts, representing the high and low parts of the input. +/// +/// # Arguments +/// * `input` - The u128 value to be split. +/// +/// # Returns +/// A tuple containing two u64 values, where the first element is the high part of the input +/// and the second element is the low part of the input. +pub fn u128_split(input: u128) -> (u64, u64) { + let (high, low) = core::integer::u128_safe_divmod( + input, 0x10000000000000000_u128.try_into().unwrap() + ); + + (high.try_into().unwrap(), low.try_into().unwrap()) +} + +/// Computes the number of 32-byte words required to represent `size` bytes +/// +/// # Arguments +/// * `size` - The size in bytes +/// +/// # Returns +/// The number of 32-byte words required to represent `size` bytes +/// +/// # Examples +/// bytes_32_words_size(2) = 1 +/// bytes_32_words_size(34) = 2 +#[inline(always)] +pub fn bytes_32_words_size(size: usize) -> usize { + size.saturating_add(31) / 32 +} + +/// Computes 256 ** (16 - i) for 0 <= i <= 16. +pub fn pow256_rev(i: usize) -> u256 { + if (i > 16) { + panic_with_felt252('pow256_rev: i > 16'); + } + let v = POW_256_REV.span().at(i); + *v +} + +/// Computes 2**pow for 0 <= pow < 128. +pub fn pow2(pow: usize) -> u128 { + if (pow > 127) { + return panic_with_felt252('pow2: pow >= 128'); + } + let v = POW_2.span().at(pow); + *v +} + +/// Splits a u256 into `len` bytes, big-endian, and appends the result to `dst`. +pub fn split_word(mut value: u256, mut len: usize, ref dst: Array) { + let word_le = split_word_le(value, len); + let word_be = ArrayExtTrait::reverse(word_le.span()); + ArrayExtTrait::concat(ref dst, word_be.span()); +} + +/// Splits a u128 into `len` bytes in little-endian order and appends them to the destination array. +/// +/// # Arguments +/// * `dest` - The destination array to append the bytes to +/// * `value` - The u128 value to split into bytes +/// * `len` - The number of bytes to split the value into +pub fn split_u128_le(ref dest: Array, mut value: u128, mut len: usize) { + for _ in 0..len { + dest.append((value % 256).try_into().unwrap()); + value /= 256; + }; +} + +/// Splits a u256 into `len` bytes, little-endian, and returns the bytes array. +/// +/// # Arguments +/// * `value` - The u256 value to be split. +/// * `len` - The number of bytes to split the value into. +/// +/// # Returns +/// An `Array` containing the little-endian byte representation of the input value. +pub fn split_word_le(mut value: u256, mut len: usize) -> Array { + let mut dst: Array = ArrayTrait::new(); + let low_len = min(len, 16); + split_u128_le(ref dst, value.low, low_len); + let high_len = min(len - low_len, 16); + split_u128_le(ref dst, value.high, high_len); + dst +} + +/// Splits a u256 into 16 bytes, big-endian, and appends the result to `dst`. +/// +/// # Arguments +/// * `value` - The u256 value to be split. +/// * `dst` - The destination array to append the bytes to. +pub fn split_word_128(value: u256, ref dst: Array) { + split_word(value, 16, ref dst) +} + +/// Converts a u256 to a bytes array represented by an array of u8 values in big-endian order. +/// +/// # Arguments +/// * `value` - The u256 value to convert. +/// +/// # Returns +/// An `Array` representing the big-endian byte representation of the input value. +pub fn u256_to_bytes_array(mut value: u256) -> Array { + let mut bytes_arr: Array = ArrayTrait::new(); + // low part + for _ in 0 + ..16_u8 { + bytes_arr.append((value.low & 0xFF).try_into().unwrap()); + value.low /= 256; + }; + + // high part + for _ in 0 + ..16_u8 { + bytes_arr.append((value.high & 0xFF).try_into().unwrap()); + value.high /= 256; + }; + + // Reverse the array as memory is arranged in big endian order. + let mut counter = bytes_arr.len(); + let mut bytes_arr_reversed: Array = ArrayTrait::new(); + while counter != 0 { + bytes_arr_reversed.append(*bytes_arr[counter - 1]); + counter -= 1; + }; + bytes_arr_reversed +} + + +/// Computes the Starknet address for a given Kakarot address, EVM address, and class hash. +/// +/// # Arguments +/// * `kakarot_address` - The Kakarot contract address. +/// * `evm_address` - The Ethereum address. +/// * `class_hash` - The class hash. +/// +/// # Returns +/// A `ContractAddress` representing the computed Starknet address. +pub fn compute_starknet_address( + kakarot_address: ContractAddress, evm_address: EthAddress, class_hash: ClassHash +) -> ContractAddress { + // Deployer is always Kakarot (current contract) + // pedersen(a1, a2, a3) is defined as: + // pedersen(pedersen(pedersen(a1, a2), a3), len([a1, a2, a3])) + // https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/hash_state.py#L6 + // https://github.com/xJonathanLEI/starknet-rs/blob/master/starknet-core/src/crypto.rs#L49 + // Constructor Calldata For an Account, the constructor calldata is: + // [1, evm_address] + let constructor_calldata_hash = PedersenTrait::new(0) + .update_with(1) + .update_with(evm_address) + .update(2) + .finalize(); + + let hash = PedersenTrait::new(0) + .update_with(CONTRACT_ADDRESS_PREFIX) + .update_with(kakarot_address) + .update_with(evm_address) + .update_with(class_hash) + .update_with(constructor_calldata_hash) + .update(5) + .finalize(); + + let normalized_address: ContractAddress = (hash.into() & MAX_ADDRESS).try_into().unwrap(); + // We know this unwrap is safe, because of the above bitwise AND on 2 ** 251 + normalized_address +} + + +#[cfg(test)] +mod tests { + use crate::helpers; + + #[test] + fn test_u256_to_bytes_array() { + let value: u256 = 256; + + let bytes_array = helpers::u256_to_bytes_array(value); + assert(1 == *bytes_array[30], 'wrong conversion'); + } + + #[test] + fn test_split_word_le() { + // Test with 0 value and 0 len + let res0 = helpers::split_word_le(0, 0); + assert(res0.len() == 0, 'res0: wrong length'); + + // Test with single byte value + let res1 = helpers::split_word_le(1, 1); + assert(res1.len() == 1, 'res1: wrong length'); + assert(*res1[0] == 1, 'res1: wrong value'); + + // Test with two byte value + let res2 = helpers::split_word_le(257, 2); // 257 = 0x0101 + assert(res2.len() == 2, 'res2: wrong length'); + assert(*res2[0] == 1, 'res2: wrong value at index 0'); + assert(*res2[1] == 1, 'res2: wrong value at index 1'); + + // Test with four byte value + let res3 = helpers::split_word_le(67305985, 4); // 67305985 = 0x04030201 + assert(res3.len() == 4, 'res3: wrong length'); + assert(*res3[0] == 1, 'res3: wrong value at index 0'); + assert(*res3[1] == 2, 'res3: wrong value at index 1'); + assert(*res3[2] == 3, 'res3: wrong value at index 2'); + assert(*res3[3] == 4, 'res3: wrong value at index 3'); + + // Test with 16 byte value (u128 max value) + let max_u128: u256 = 340282366920938463463374607431768211454; // u128 max value - 1 + let res4 = helpers::split_word_le(max_u128, 16); + assert(res4.len() == 16, 'res4: wrong length'); + assert(*res4[0] == 0xfe, 'res4: wrong MSB value'); + + for counter in 1..16_u32 { + assert(*res4[counter] == 0xff, 'res4: wrong value at index'); + }; + } + + #[test] + fn test_split_word() { + // Test with 0 value and 0 len + let mut dst0: Array = ArrayTrait::new(); + helpers::split_word(0, 0, ref dst0); + assert(dst0.len() == 0, 'dst0: wrong length'); + + // Test with single byte value + let mut dst1: Array = ArrayTrait::new(); + helpers::split_word(1, 1, ref dst1); + assert(dst1.len() == 1, 'dst1: wrong length'); + assert(*dst1[0] == 1, 'dst1: wrong value'); + + // Test with two byte value + let mut dst2: Array = ArrayTrait::new(); + helpers::split_word(257, 2, ref dst2); // 257 = 0x0101 + assert(dst2.len() == 2, 'dst2: wrong length'); + assert(*dst2[0] == 1, 'dst2: wrong value at index 0'); + assert(*dst2[1] == 1, 'dst2: wrong value at index 1'); + + // Test with four byte value + let mut dst3: Array = ArrayTrait::new(); + helpers::split_word(16909060, 4, ref dst3); // 16909060 = 0x01020304 + assert(dst3.len() == 4, 'dst3: wrong length'); + assert(*dst3[0] == 1, 'dst3: wrong value at index 0'); + assert(*dst3[1] == 2, 'dst3: wrong value at index 1'); + assert(*dst3[2] == 3, 'dst3: wrong value at index 2'); + assert(*dst3[3] == 4, 'dst3: wrong value at index 3'); + + // Test with 16 byte value (u128 max value) + let max_u128: u256 = 340282366920938463463374607431768211454; // u128 max value -1 + let mut dst4: Array = ArrayTrait::new(); + helpers::split_word(max_u128, 16, ref dst4); + assert(dst4.len() == 16, 'dst4: wrong length'); + assert(*dst4[15] == 0xfe, 'dst4: wrong LSB value'); + for counter in 0..dst4.len() - 1 { + assert_eq!(*dst4[counter], 0xff); + }; + } + + #[test] + fn test_bytes_32_words_size_edge_case() { + let max_usize = core::num::traits::Bounded::::MAX; + assert_eq!(helpers::bytes_32_words_size(max_usize), (max_usize / 32)); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/i256.cairo b/cairo/kakarot-ssj/crates/utils/src/i256.cairo new file mode 100644 index 000000000..9e8bf5dda --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/i256.cairo @@ -0,0 +1,535 @@ +use core::num::traits::Bounded; +use crate::constants::POW_2_127; + +/// Represents a signed 256-bit integer. +#[derive(Copy, Drop, PartialEq)] +pub struct i256 { + /// The underlying unsigned 256-bit value. + pub value: u256, +} + + +pub impl U256IntoI256 of Into { + #[inline(always)] + fn into(self: u256) -> i256 { + i256 { value: self } + } +} + +pub impl I256IntoU256 of Into { + #[inline(always)] + fn into(self: i256) -> u256 { + self.value + } +} + +pub impl I256PartialOrd of PartialOrd { + #[inline(always)] + fn le(lhs: i256, rhs: i256) -> bool { + !(rhs < lhs) + } + + #[inline(always)] + fn ge(lhs: i256, rhs: i256) -> bool { + !(lhs < rhs) + } + + #[inline(always)] + fn lt(lhs: i256, rhs: i256) -> bool { + let lhs_positive = lhs.value.high < POW_2_127; + let rhs_positive = rhs.value.high < POW_2_127; + + if (lhs_positive != rhs_positive) { + !lhs_positive + } else { + lhs.value < rhs.value + } + } + + #[inline(always)] + fn gt(lhs: i256, rhs: i256) -> bool { + let lhs_positive = lhs.value.high < POW_2_127; + let rhs_positive = rhs.value.high < POW_2_127; + + if (lhs_positive != rhs_positive) { + lhs_positive + } else { + lhs.value > rhs.value + } + } +} + +pub impl I256Div of Div { + fn div(lhs: i256, rhs: i256) -> i256 { + let (q, _) = i256_signed_div_rem(lhs, rhs.value.try_into().expect('Division by 0')); + return q.into(); + } +} + +pub impl I256Rem of Rem { + fn rem(lhs: i256, rhs: i256) -> i256 { + let (_, r) = i256_signed_div_rem(lhs, rhs.value.try_into().expect('Division by 0')); + return r.into(); + } +} + +/// Performs signed integer division between two integers. +/// +/// This function conforms to EVM specifications, except that the type system enforces div != zero. +/// See ethereum yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf, page 29). +/// +/// Note: +/// - The remainder may be negative if one of the inputs is negative. +/// - (-2**255) / (-1) = -2**255 because 2**255 is out of range. +/// +/// # Arguments +/// +/// * `a` - The dividend. +/// * `div` - The divisor, passed as a signed NonZero. +/// +/// # Returns +/// +/// A tuple containing (quotient, remainder) of the signed division of `a` by `div`. +fn i256_signed_div_rem(a: i256, div: NonZero) -> (i256, i256) { + let mut div = i256 { value: div.into() }; + + // When div=-1, simply return -a. + if div.value == Bounded::::MAX { + return (i256_neg(a).into(), 0_u256.into()); + } + + // Take the absolute value of a and div. + // Checks the MSB bit sign for a 256-bit integer + let a_positive = a.value.high < POW_2_127; + let a = if a_positive { + a + } else { + i256_neg(a).into() + }; + + let div_positive = div.value.high < POW_2_127; + div = if div_positive { + div + } else { + i256_neg(div).into() + }; + + // Compute the quotient and remainder. + // Can't panic as zero case is handled in the first instruction + let (quot, rem) = DivRem::div_rem(a.value, div.value.try_into().unwrap()); + + // Restore remainder sign. + let rem = if a_positive { + rem.into() + } else { + i256_neg(rem.into()) + }; + + // If the signs of a and div are the same, return the quotient and remainder. + if a_positive == div_positive { + return (quot.into(), rem.into()); + } + + // Otherwise, return the negation of the quotient and the remainder. + (i256_neg(quot.into()), rem.into()) +} + +/// Computes the negation of an i256 integer. +/// +/// Note that the negation of -2**255 is -2**255. +/// +/// # Arguments +/// +/// * `a` - The i256 value to negate. +/// +/// # Returns +/// +/// The negation of the input value. +fn i256_neg(a: i256) -> i256 { + // If a is 0, adding one to its bitwise NOT will overflow and return 0. + if a.value == 0 { + return 0_u256.into(); + } + (~a.value + 1).into() +} + +#[cfg(test)] +mod tests { + use core::num::traits::Bounded; + use crate::i256::{i256, i256_neg, i256_signed_div_rem}; + + const MAX_SIGNED_VALUE: u256 = + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + const MIN_SIGNED_VALUE: u256 = + 0x8000000000000000000000000000000000000000000000000000000000000000; + + #[test] + fn test_i256_eq() { + let val: i256 = 1_u256.into(); + + assert(val == 1_u256.into(), 'i256 should be eq'); + } + + #[test] + fn test_i256_ne() { + let val: i256 = 1_u256.into(); + + assert(val != 2_u256.into(), 'i256 should be ne'); + } + + #[test] + fn test_i256_positive() { + let val: i256 = MAX_SIGNED_VALUE.into(); + + assert(val > 0_u256.into(), 'i256 should be positive'); + } + + #[test] + fn test_i256_negative() { + let val: i256 = Bounded::::MAX.into(); // -1 + + assert(val < 0_u256.into(), 'i256 should be negative'); + } + + #[test] + fn test_lt_positive_positive() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = 2_u256.into(); + + assert(lhs < rhs, 'lhs should be lt rhs'); + } + + #[test] + fn test_lt_negative_negative() { + let lhs: i256 = (Bounded::::MAX - 1).into(); // -2 + let rhs: i256 = Bounded::::MAX.into(); // -1 + + assert(lhs < rhs, 'lhs should be lt rhs'); + } + + #[test] + fn test_lt_negative_positive() { + let lhs: i256 = Bounded::::MAX.into(); // -1 + let rhs: i256 = 1_u256.into(); + + assert(lhs < rhs, 'lhs should be lt rhs'); + } + + #[test] + fn test_lt_positive_negative() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = Bounded::::MAX.into(); // -1 + + assert(!(lhs < rhs), 'lhs should not be lt rhs'); + } + + #[test] + fn test_lt_equals() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = 1_u256.into(); + + assert(!(lhs < rhs), 'lhs should not be lt rhs'); + } + + #[test] + fn test_le_positive_positive() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = 2_u256.into(); + + assert(lhs <= rhs, 'lhs should be le rhs'); + } + + #[test] + fn test_le_negative_negative() { + let lhs: i256 = (Bounded::::MAX - 1).into(); // -2 + let rhs: i256 = Bounded::::MAX.into(); // -1 + + assert(lhs <= rhs, 'lhs should be le rhs'); + } + + #[test] + fn test_le_negative_positive() { + let lhs: i256 = Bounded::::MAX.into(); // -1 + let rhs: i256 = 1_u256.into(); + + assert(lhs <= rhs, 'lhs should be le rhs'); + } + + #[test] + fn test_le_positive_negative() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = Bounded::::MAX.into(); // -1 + + assert(!(lhs <= rhs), 'lhs should not be le rhs'); + } + + #[test] + fn test_le_equals() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = 1_u256.into(); + + assert(lhs <= rhs, 'lhs should be le rhs'); + } + + #[test] + fn test_gt_positive_positive() { + let lhs: i256 = 2_u256.into(); + let rhs: i256 = 1_u256.into(); + + assert(lhs > rhs, 'lhs should be gt rhs'); + } + + #[test] + fn test_gt_negative_negative() { + let lhs: i256 = Bounded::::MAX.into(); // -1 + let rhs: i256 = (Bounded::::MAX - 1).into(); // -2 + + assert(lhs > rhs, 'lhs should be gt rhs'); + } + + #[test] + fn test_gt_negative_positive() { + let lhs: i256 = Bounded::::MAX.into(); // -1 + let rhs: i256 = 1_u256.into(); + + assert(!(lhs > rhs), 'lhs should not be gt rhs'); + } + + #[test] + fn test_gt_positive_negative() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = Bounded::::MAX.into(); // -1 + + assert(lhs > rhs, 'lhs should be gt rhs'); + } + + #[test] + fn test_gt_equals() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = 1_u256.into(); + + assert(!(lhs > rhs), 'lhs should not be gt rhs'); + } + + #[test] + fn test_ge_positive_positive() { + let lhs: i256 = 2_u256.into(); + let rhs: i256 = 1_u256.into(); + + assert(lhs >= rhs, 'lhs should be ge rhs'); + } + + #[test] + fn test_ge_negative_negative() { + let lhs: i256 = Bounded::::MAX.into(); // -1 + let rhs: i256 = (Bounded::::MAX - 1).into(); // -2 + + assert(lhs >= rhs, 'lhs should be ge rhs'); + } + + #[test] + fn test_ge_negative_positive() { + let lhs: i256 = Bounded::::MAX.into(); // -1 + let rhs: i256 = 1_u256.into(); + + assert(!(lhs >= rhs), 'lhs should not be ge rhs'); + } + + #[test] + fn test_ge_positive_negative() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = Bounded::::MAX.into(); // -1 + + assert(lhs >= rhs, 'lhs should be ge rhs'); + } + + #[test] + fn test_ge_equals() { + let lhs: i256 = 1_u256.into(); + let rhs: i256 = 1_u256.into(); + + assert(lhs >= rhs, 'lhs should be ge rhs'); + } + + #[test] + fn test_i256_neg() { + let max_u256 = Bounded::::MAX; + let x = i256_neg(1_u256.into()); + // 0000_0001 turns into 1111_1110 + 1 = 1111_1111 + assert(x.value.low == max_u256.low && x.value.high == max_u256.high, 'i256_neg failed.'); + + let x = i256_neg(0_u256.into()); + // 0000_0000 turns into 1111_1111 + 1 = 0000_0000 + assert(x == 0_u256.into(), 'i256_neg with zero failed.'); + + let x = max_u256; + // 1111_1111 turns into 0000_0000 + 1 = 0000_0001 + assert(i256_neg(x.into()) == 1_u256.into(), 'i256_neg with max_u256 failed.'); + } + + #[test] + fn test_signed_div_rem() { + let max_u256 = Bounded::::MAX; + let max_i256 = i256 { value: max_u256 }; + // Division by -1 + assert( + i256_signed_div_rem( + i256 { value: 1 }, max_u256.try_into().unwrap() + ) == (max_i256, 0_u256.into()), + 'Division by -1 failed - 1.' + ); // 1 / -1 == -1 + assert( + i256_signed_div_rem( + max_i256, max_u256.try_into().unwrap() + ) == (i256 { value: 1 }, 0_u256.into()), + 'Division by -1 failed - 2.' + ); // -1 / -1 == 1 + assert( + i256_signed_div_rem( + i256 { value: 0 }, max_u256.try_into().unwrap() + ) == (i256 { value: 0 }, 0_u256.into()), + 'Division by -1 failed - 3.' + ); // 0 / -1 == 0 + + // Simple Division + assert( + i256_signed_div_rem( + i256 { value: 10 }, 2_u256.try_into().unwrap() + ) == (i256 { value: 5 }, 0_u256.into()), + 'Simple Division failed - 1.' + ); // 10 / 2 == 5 + assert( + i256_signed_div_rem( + i256 { value: 10 }, 3_u256.try_into().unwrap() + ) == (i256 { value: 3 }, 1_u256.into()), + 'Simple Division failed - 2.' + ); // 10 / 3 == 3 remainder 1 + + // Dividing a Negative Number + assert( + i256_signed_div_rem(max_i256, 1_u256.try_into().unwrap()) == (max_i256, 0_u256.into()), + 'Dividing a neg num failed - 1.' + ); // -1 / 1 == -1 + assert( + i256_signed_div_rem( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE }, + 0x2_u256.try_into().unwrap() + ) == (max_i256, 0_u256.into()), + 'Dividing a neg num failed - 2.' + ); // -2 / 2 == -1 + // - 2**255 / 2 == - 2**254 + assert( + i256_signed_div_rem( + i256 { value: 0x8000000000000000000000000000000000000000000000000000000000000000 }, + 0x2_u256.try_into().unwrap() + ) == ( + i256 { value: 0xc000000000000000000000000000000000000000000000000000000000000000 }, + 0_u256.into() + ), + 'Dividing a neg num failed - 3.' + ); + + // Dividing by a Negative Number + assert( + i256_signed_div_rem( + i256 { value: 0x4 }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE_u256 + .try_into() + .unwrap() + ) == ( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE }, + 0_u256.into() + ), + 'Div by a neg num failed - 1.' + ); // 4 / -2 == -2 + assert( + i256_signed_div_rem( + i256 { value: MAX_SIGNED_VALUE }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_u256 + .try_into() + .unwrap() + ) == (i256 { value: (MIN_SIGNED_VALUE + 1) }, 0_u256.into()), + 'Div by a neg num failed - 2.' + ); // MAX_VALUE (2**255 -1) / -1 == MIN_VALUE + 1 + assert( + i256_signed_div_rem( + i256 { value: 0x1 }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_u256 + .try_into() + .unwrap() + ) == ( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF }, + 0_u256.into() + ), + 'Div by a neg num failed - 3.' + ); // 1 / -1 == -1 + + // Both Dividend and Divisor Negative + assert( + i256_signed_div_rem( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB_u256 + .try_into() + .unwrap() + ) == (i256 { value: 2 }, 0_u256.into()), + 'Div w/ both neg num failed - 1.' + ); // -10 / -5 == 2 + assert( + i256_signed_div_rem( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5_u256 + .try_into() + .unwrap() + ) == ( + i256 { value: 0 }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6_u256.into() + ), + 'Div w/ both neg num failed - 2.' + ); // -10 / -11 == 0 remainder -10 + + // Division with Remainder + assert( + i256_signed_div_rem( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 }, + 0x3_u256.try_into().unwrap() + ) == ( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_u256.into() + ), + 'Div with rem failed - 1.' + ); // -7 / 3 == -2 remainder -1 + assert( + i256_signed_div_rem( + i256 { value: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF }, + 0x2_u256.try_into().unwrap() + ) == ( + i256 { value: 0 }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_u256.into() + ), + 'Div with rem failed - 2.' + ); // -1 / 2 == 0 remainder -1 + + // Edge Case: Dividing Minimum Value by -1 + assert( + i256_signed_div_rem( + i256 { value: MIN_SIGNED_VALUE }, + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_u256 + .try_into() + .unwrap() + ) == (i256 { value: MIN_SIGNED_VALUE }, 0_u256.into()), + 'Div w/ both neg num failed - 3.' + ); // MIN / -1 == MIN because 2**255 is out of range + } + + #[test] + #[should_panic(expected: ('Option::unwrap failed.',))] + fn test_signed_div_rem_by_zero() { + // Zero Division + assert( + i256_signed_div_rem( + i256 { value: 0 }, 0_u256.try_into().unwrap() + ) == (i256 { value: 0 }, i256 { value: 0 }), + 'Zero Division failed - 1.' + ); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/lib.cairo b/cairo/kakarot-ssj/crates/utils/src/lib.cairo new file mode 100644 index 000000000..05a2f2975 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/lib.cairo @@ -0,0 +1,16 @@ +pub mod address; +pub mod constants; +pub mod crypto; +pub mod errors; +pub mod eth_transaction; +pub mod felt_vec; +pub mod fmt; +pub mod helpers; +pub mod i256; +pub mod math; +pub mod rlp; +pub mod serialization; +pub mod set; +pub mod test_data; +pub mod traits; +pub mod utils; diff --git a/cairo/kakarot-ssj/crates/utils/src/math.cairo b/cairo/kakarot-ssj/crates/utils/src/math.cairo new file mode 100644 index 000000000..ec1344b2d --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/math.cairo @@ -0,0 +1,730 @@ +use core::integer::{u512}; +use core::num::traits::{Zero, One, BitSize, OverflowingAdd, OverflowingMul, Bounded}; +use core::panic_with_felt252; +use core::traits::{BitAnd}; + +// === Exponentiation === + +pub trait Exponentiation { + /// Raise a number to a power. + /// + /// # Arguments + /// + /// * `self` - The base number + /// * `exponent` - The exponent to raise the base to + /// + /// # Returns + /// + /// The result of raising `self` to the power of `exponent` + /// + /// # Panics + /// + /// Panics if the result overflows the type T. + fn pow(self: T, exponent: T) -> T; +} + +impl ExponentiationImpl< + T, + +Zero, + +One, + +Add, + +Sub, + +Mul, + +Div, + +BitAnd, + +PartialEq, + +Copy, + +Drop +> of Exponentiation { + fn pow(self: T, mut exponent: T) -> T { + let zero = Zero::zero(); + if self.is_zero() { + return zero; + } + let one = One::one(); + let mut result = one; + let mut base = self; + let two = one + one; + + loop { + if exponent & one == one { + result = result * base; + } + + exponent = exponent / two; + if exponent == zero { + break result; + } + + base = base * base; + } + } +} + +pub trait WrappingExponentiation { + /// Raise a number to a power modulo MAX (max value of type T). + /// Instead of explicitly providing a modulo, we use overflowing functions + /// from the core library, which wrap around when overflowing. + /// + /// # Arguments + /// + /// * `self` - The base number + /// * `exponent` - The exponent to raise the base to + /// + /// # Returns + /// + /// The result of base raised to the power of exp modulo MAX. + fn wrapping_pow(self: T, exponent: T) -> T; + + /// Performs exponentiation by repeatedly multiplying the base number with itself. + /// + /// This function uses a simple loop to perform exponentiation. It continues to multiply + /// the base number (`self`) with itself, for the number of times specified by `exponent`. + /// The method uses a wrapping strategy to handle overflow, which means if the result + /// overflows the type `T`, then higher bits are discarded and the result is wrapped. + /// + /// # Arguments + /// + /// * `self` - The base number of type `T`. + /// * `exponent` - The exponent to which the base number is raised, also of type `T`. + /// + /// # Returns + /// + /// The result of raising `self` to the power of `exponent`, of type `T`. + /// The result is wrapped in case of overflow. + fn wrapping_spow(self: T, exponent: T) -> T; + + /// Performs exponentiation using the binary exponentiation method. + /// + /// This function calculates the power of a number using binary exponentiation, which is + /// an optimized method for exponentiation that reduces the number of multiplications. + /// It works by repeatedly squaring the base and reducing the exponent by half, using + /// a wrapping strategy to handle overflow. This means if intermediate or final results + /// overflow the type `T`, then the higher bits are discarded and the result is wrapped. + /// + /// # Arguments + /// + /// * `self` - The base number of type `T`. + /// * `exponent` - The exponent to which the base number is raised, also of type `T`. + /// + /// # Returns + /// + /// The result of raising `self` to the power of `exponent`, of type `T`. + /// The result is wrapped in case of overflow. + fn wrapping_fpow(self: T, exponent: T) -> T; +} + + +pub impl WrappingExponentiationImpl< + T, + +OverflowingMul, + +Zero, + +One, + +Add, + +Mul, + +Div, + +Rem, + +Copy, + +Drop, + +PartialEq, + +PartialOrd, + +core::ops::SubAssign +> of WrappingExponentiation { + fn wrapping_pow(self: T, exponent: T) -> T { + if exponent == Zero::zero() { + return One::one(); + } + + if self == Zero::zero() { + return Zero::zero(); + } + + let one = One::::one(); + let ten = one + one + one + one + one + one + one + one + one + one; + + if exponent > ten { + self.wrapping_fpow(exponent) + } else { + self.wrapping_spow(exponent) + } + } + + fn wrapping_spow(self: T, exponent: T) -> T { + let mut exponent = exponent; + let mut base = self; + let mut result = One::one(); + + while exponent != Zero::zero() { + let (new_result, _) = result.overflowing_mul(base); + result = new_result; + exponent -= One::one(); + }; + result + } + + fn wrapping_fpow(self: T, exponent: T) -> T { + let mut result = One::one(); + let mut base = self; + let mut exponent = exponent; + let two = One::::one() + One::::one(); + + loop { + if exponent % two != Zero::zero() { + let (new_result, _) = result.overflowing_mul(base); + result = new_result; + } + + exponent = exponent / two; + if exponent == Zero::zero() { + break result; + } + + let (new_base, _) = base.overflowing_mul(base); + base = new_base; + } + } +} + +// === BitShift === + +pub trait Bitshift { + /// Shift a number left by a given number of bits. + /// + /// # Arguments + /// + /// * `self` - The number to shift + /// * `shift` - The number of bits to shift by + /// + /// # Returns + /// + /// The result of shifting `self` left by `shift` bits + /// + /// # Panics + /// + /// Panics if the shift is greater than 255. + /// Panics if the result overflows the type T. + fn shl(self: T, shift: usize) -> T; + + /// Shift a number right by a given number of bits. + /// + /// # Arguments + /// + /// * `self` - The number to shift + /// * `shift` - The number of bits to shift by + /// + /// # Returns + /// + /// The result of shifting `self` right by `shift` bits + /// + /// # Panics + /// + /// Panics if the shift is greater than 255. + fn shr(self: T, shift: usize) -> T; +} + +impl BitshiftImpl< + T, + +Zero, + +One, + +Add, + +Sub, + +Div, + +Mul, + +Exponentiation, + +Copy, + +Drop, + +PartialOrd, + +BitSize, + +TryInto, + +TryInto, + +TryInto, +> of Bitshift { + fn shl(self: T, shift: usize) -> T { + // if we shift by more than nb_bits of T, the result is 0 + // we early return to save gas and prevent unexpected behavior + if shift > BitSize::::bits() - One::one() { + panic_with_felt252('mul Overflow'); + } + let two = One::one() + One::one(); + self * two.pow(shift.try_into().expect('mul Overflow')) + } + + fn shr(self: T, shift: usize) -> T { + // early return to save gas if shift > nb_bits of T + if shift > BitSize::::bits() - One::one() { + panic_with_felt252('mul Overflow'); + } + let two = One::one() + One::one(); + self / two.pow(shift.try_into().expect('mul Overflow')) + } +} + +pub trait WrappingBitshift { + /// Shift a number left by a given number of bits. + /// If the shift is greater than 255, the result is 0. + /// The bits moved after the 256th one are discarded, the new bits are set to 0. + /// + /// # Arguments + /// + /// * `self` - The number to shift + /// * `shift` - The number of bits to shift by + /// + /// # Returns + /// + /// The result of shifting `self` left by `shift` bits, wrapped if necessary + fn wrapping_shl(self: T, shift: usize) -> T; + + /// Shift a number right by a given number of bits. + /// If the shift is greater than 255, the result is 0. + /// + /// # Arguments + /// + /// * `self` - The number to shift + /// * `shift` - The number of bits to shift by + /// + /// # Returns + /// + /// The result of shifting `self` right by `shift` bits, or 0 if shift > 255 + fn wrapping_shr(self: T, shift: usize) -> T; +} + +pub impl WrappingBitshiftImpl< + T, + +Zero, + +One, + +Add, + +Sub, + +Div, + +Exponentiation, + +PartialOrd, + +Drop, + +Copy, + +OverflowingMul, + +WrappingExponentiation, + +BitSize, + +Bounded, + +TryInto, + +TryInto, + +TryInto, + +Into +> of WrappingBitshift { + fn wrapping_shl(self: T, shift: usize) -> T { + let two = One::::one() + One::::one(); + let (result, _) = self.overflowing_mul(two.wrapping_pow(shift.try_into().unwrap())); + result + } + + fn wrapping_shr(self: T, shift: usize) -> T { + let two = One::::one() + One::::one(); + + if shift > BitSize::::bits() - One::one() { + return Zero::zero(); + } + self / two.pow(shift.try_into().unwrap()) + } +} + +// === Standalone functions === + +/// Adds two 256-bit unsigned integers, returning a 512-bit unsigned integer result. +/// +/// limb3 will always be 0, because the maximum sum of two 256-bit numbers is at most +/// 2**257 - 2 which fits in 257 bits. +/// +/// # Arguments +/// +/// * `a` - First 256-bit unsigned integer +/// * `b` - Second 256-bit unsigned integer +/// +/// # Returns +/// +/// A 512-bit unsigned integer representing the sum of `a` and `b` +pub fn u256_wide_add(a: u256, b: u256) -> u512 { + let (sum, overflow) = a.overflowing_add(b); + + let limb0 = sum.low; + let limb1 = sum.high; + + let limb2 = if overflow { + 1 + } else { + 0 + }; + + let limb3 = 0; + + u512 { limb0, limb1, limb2, limb3 } +} + +#[cfg(test)] +mod tests { + use core::integer::{u512}; + use core::num::traits::{OverflowingMul, WrappingMul, SaturatingAdd, Bounded}; + use crate::math::{ + Exponentiation, WrappingExponentiation, u256_wide_add, Bitshift, WrappingBitshift, + }; + use super::OverflowingAdd; + + #[test] + fn test_wrapping_pow() { + assert(5_u256.wrapping_pow(10) == 9765625, '5^10 should be 9765625'); + assert( + 5_u256 + .wrapping_pow( + 90 + ) == 807793566946316088741610050849573099185363389551639556884765625, + '5^90 failed' + ); + assert(2_u256.wrapping_pow(256) == 0, 'should wrap to 0'); + assert(123456_u256.wrapping_pow(0) == 1, 'n^0 should be 1'); + assert(0_u256.wrapping_pow(123456) == 0, '0^n should be 0'); + } + + #[test] + fn test_pow() { + assert(5_u256.pow(10) == 9765625, '5^10 should be 9765625'); + assert(5_u256.pow(45) == 28421709430404007434844970703125, '5^45 failed'); + assert(123456_u256.pow(0) == 1, 'n^0 should be 1'); + assert(0_u256.pow(123456) == 0, '0^n should be 0'); + } + + #[test] + fn test_wrapping_fast_pow() { + let exp = 3_u256.wrapping_fpow(10); + assert( + 3_u256 + .wrapping_fpow( + exp + ) == 6701808933569337837891967767170127839253608180143676463326689955522159283811, + '3^(3^10) failed' + ); + } + + #[test] + fn test_wrapping_fast_pow_0() { + assert(3_u256.wrapping_fpow(0) == 1, '3^(0) should be 1'); + } + + #[test] + fn test_wrapping_fast_base_0() { + assert(0_u256.wrapping_fpow(42) == 0, '0^(42) should be 0'); + } + + #[test] + fn test_wrapping_fast_base_0_pow_0() { + assert(0_u256.wrapping_fpow(0) == 1, '0^(0) should be 1'); + } + + #[test] + #[should_panic(expected: ('u256_mul Overflow',))] + fn test_pow_should_overflow() { + 2_u256.pow(256); + } + + + #[test] + fn test_wide_add_basic() { + let a = 1000; + let b = 500; + + let (_, overflow) = a.overflowing_add(b); + + let expected = u512 { limb0: 1500, limb1: 0, limb2: 0, limb3: 0, }; + + let result = u256_wide_add(a, b); + + assert(!overflow, 'shouldnt overflow'); + assert(result == expected, 'wrong result'); + } + + #[test] + fn test_wide_add_overflow() { + let a = Bounded::::MAX; + let b = 1; + + let (_, overflow) = a.overflowing_add(b); + + let expected = u512 { limb0: 0, limb1: 0, limb2: 1, limb3: 0, }; + + let result = u256_wide_add(a, b); + + assert(overflow, 'should overflow'); + assert(result == expected, 'wrong result'); + } + + #[test] + fn test_wide_add_max_values() { + let a = Bounded::::MAX; + let b = Bounded::::MAX; + + let expected = u512 { + limb0: 0xfffffffffffffffffffffffffffffffe, + limb1: 0xffffffffffffffffffffffffffffffff, + limb2: 1, + limb3: 0, + }; + + let result = u256_wide_add(a, b); + + assert(result == expected, 'wrong result'); + } + + #[test] + fn test_shl() { + // Given + let a = 0x00000091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aab3f_u256; + // 1-byte shift is an 8-bit shift + let shift = 3 * 8; + + // When + let result = a.shl(shift); + + // Then + let expected = 0x91b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aab3f000000_u256; + assert(result == expected, 'wrong result'); + } + + + #[test] + #[should_panic(expected: ('mul Overflow',))] + fn test_shl_256_bits_overflow() { + // Given + let a = 0x00000091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498faab3fe_u256; + // 1-byte shift is an 8-bit shift + let shift = 32 * 8; + + // When & Then 2.pow(256) overflows u256 + a.shl(shift); + } + + #[test] + #[should_panic(expected: ('u256_mul Overflow',))] + fn test_shl_overflow() { + // Given + let a = 0x00000091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498faab3fe_u256; + // 1-byte shift is an 8-bit shift + let shift = 4 * 8; + + // When & Then a << 32 overflows u256 + a.shl(shift); + } + + #[test] + fn test_wrapping_shl_overflow() { + // Given + let a = 0x00000091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498faab3fe_u256; + // 1-byte shift is an 8-bit shift + let shift = 12 * 8; + + // When + let result = a.wrapping_shl(shift); + + // Then + // The bits moved after the 256th one are discarded, the new bits are set to 0. + let expected = 0xf24201bac4e64f70ca2b9d9491e82a498faab3fe000000000000000000000000_u256; + assert(result == expected, 'wrong result'); + } + + + #[test] + fn test_wrapping_shl() { + // Given + let a = 0x00000091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aab3f_u256; + // 1-byte shift is an 8-bit shift + let shift = 3 * 8; + + // When + let result = a.wrapping_shl(shift); + + // Then + let expected = 0x91b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aab3f000000_u256; + assert(result == expected, 'wrong result'); + } + + #[test] + fn test_shr() { + // Given + let a = 0x0091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aade6263a_u256; + // 1-byte shift is an 8-bit shift + let shift = 1 * 8; + + // When + let result = a.shr(shift); + + // Then + let expected = 0x000091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aade626_u256; + assert(result == expected, 'wrong result'); + } + + #[test] + #[should_panic(expected: ('mul Overflow',))] + fn test_shr_256_bits_overflow() { + let a = 0xab91b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aade6263a_u256; + let shift = 32 * 8; + + // When & Then 2.pow(256) overflows u256 + a.shr(shift); + } + + + #[test] + fn test_wrapping_shr() { + // Given + let a = 0x0091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aade6263a_u256; + // 1-byte shift is an 8-bit shift + let shift = 2 * 8; + + // When + let result = a.wrapping_shr(shift); + + // Then + let expected = 0x00000091b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aade6_u256; + assert(result == expected, 'wrong result'); + } + + + #[test] + fn test_wrapping_shr_to_zero() { + // Given + let a = 0xab91b2efa2bfd58aee61f24201bac4e64f70ca2b9d9491e82a498f2aade6263a_u256; + // 1-byte shift is an 8-bit shift + let shift = 32 * 8; + + // When + let result = a.wrapping_shr(shift); + + // Then + let expected = 0_u256; + assert(result == expected, 'wrong result'); + } + + #[test] + fn test_u8_overflowing_mul_not_overflow_case() { + let result = 5_u8.overflowing_mul(10); + assert_eq!(result, (50, false)); + } + + #[test] + fn test_u8_overflowing_mul_overflow_case() { + let result = Bounded::::MAX.overflowing_mul(Bounded::MAX); + assert_eq!(result, (1, true)); + } + + #[test] + fn test_u8_wrapping_mul_not_overflow_case() { + let result = 5_u8.wrapping_mul(10); + assert_eq!(result, 50); + } + + #[test] + fn test_u8_wrapping_mul_overflow_case() { + let result = Bounded::::MAX.wrapping_mul(Bounded::MAX); + assert_eq!(result, 1); + } + + #[test] + fn test_u32_overflowing_mul_not_overflow_case() { + let result = 5_u32.overflowing_mul(10); + assert_eq!(result, (50, false)); + } + + #[test] + fn test_u32_overflowing_mul_overflow_case() { + let result = Bounded::::MAX.overflowing_mul(Bounded::MAX); + assert_eq!(result, (1, true)); + } + + #[test] + fn test_u32_wrapping_mul_not_overflow_case() { + let result = 5_u32.wrapping_mul(10); + assert_eq!(result, 50); + } + + #[test] + fn test_u32_wrapping_mul_overflow_case() { + let result = Bounded::::MAX.wrapping_mul(Bounded::MAX); + assert_eq!(result, 1); + } + + + #[test] + fn test_u64_overflowing_mul_not_overflow_case() { + let result = 5_u64.overflowing_mul(10); + assert_eq!(result, (50, false)); + } + + #[test] + fn test_u64_overflowing_mul_overflow_case() { + let result = Bounded::::MAX.overflowing_mul(Bounded::MAX); + assert_eq!(result, (1, true)); + } + + #[test] + fn test_u64_wrapping_mul_not_overflow_case() { + let result = 5_u64.wrapping_mul(10); + assert_eq!(result, 50); + } + + #[test] + fn test_u64_wrapping_mul_overflow_case() { + let result = Bounded::::MAX.wrapping_mul(Bounded::MAX); + assert_eq!(result, 1); + } + + + #[test] + fn test_u128_overflowing_mul_not_overflow_case() { + let result = 5_u128.overflowing_mul(10); + assert_eq!(result, (50, false)); + } + + #[test] + fn test_u128_overflowing_mul_overflow_case() { + let result = Bounded::::MAX.overflowing_mul(Bounded::MAX); + assert_eq!(result, (1, true)); + } + + #[test] + fn test_u128_wrapping_mul_not_overflow_case() { + let result = 5_u128.wrapping_mul(10); + assert_eq!(result, 50); + } + + #[test] + fn test_u128_wrapping_mul_overflow_case() { + let result = Bounded::::MAX.wrapping_mul(Bounded::MAX); + assert_eq!(result, 1); + } + + #[test] + fn test_u256_overflowing_mul_not_overflow_case() { + let result = 5_u256.overflowing_mul(10); + assert_eq!(result, (50, false)); + } + + #[test] + fn test_u256_overflowing_mul_overflow_case() { + let result = Bounded::::MAX.overflowing_mul(Bounded::MAX); + assert_eq!(result, (1, true)); + } + + #[test] + fn test_u256_wrapping_mul_not_overflow_case() { + let result = 5_u256.wrapping_mul(10); + assert_eq!(result, 50); + } + + #[test] + fn test_u256_wrapping_mul_overflow_case() { + let result = Bounded::::MAX.wrapping_mul(Bounded::MAX); + assert_eq!(result, 1); + } + + #[test] + fn test_saturating_add() { + let max = Bounded::::MAX; + + assert_eq!(max.saturating_add(1), Bounded::::MAX); + assert_eq!((max - 2).saturating_add(1), max - 1); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/rlp.cairo b/cairo/kakarot-ssj/crates/utils/src/rlp.cairo new file mode 100644 index 000000000..9c991671e --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/rlp.cairo @@ -0,0 +1,2766 @@ +use core::array::ArrayTrait; +use core::array::SpanTrait; +use core::option::OptionTrait; +use core::panic_with_felt252; +use core::starknet::EthAddress; +use crate::errors::{RLPError}; +use crate::eth_transaction::eip2930::AccessListItem; +use crate::traits::array::ArrayExtension; +use crate::traits::bytes::{ToBytes, FromBytes}; +use crate::traits::eth_address::EthAddressExTrait; + +// Possible RLP types +#[derive(Drop, PartialEq, Debug)] +pub enum RLPType { + String, + List +} + +#[derive(Drop, Copy, PartialEq, Debug)] +pub enum RLPItem { + String: Span, + List: Span +} + +#[generate_trait] +pub impl RLPImpl of RLPTrait { + /// Returns RLPType from the leading byte with + /// its offset in the array as well as its size. + /// + /// # Arguments + /// * `input` - Span of bytes to decode + /// + /// # Returns + /// * `Result<(RLPType, u32, u32), RLPError>` - A tuple containing the RLPType, + /// the offset, and the size of the RLPItem to decode + /// + /// # Errors + /// * `RLPError::EmptyInput` - if the input is empty + /// * `RLPError::InputTooShort` - if the input is too short for a given type + /// * `RLPError::InvalidInput` - if the input is invalid + fn decode_type(input: Span) -> Result<(RLPType, u32, u32), RLPError> { + let input_len = input.len(); + if input_len == 0 { + return Result::Err(RLPError::EmptyInput); + } + + let prefix = *input[0]; + + if prefix < 0x80 { // Char + Result::Ok((RLPType::String, 0, 1)) + } else if prefix < 0xb8 { // Short String + Result::Ok((RLPType::String, 1, prefix.into() - 0x80)) + } else if prefix < 0xc0 { // Long String + let len_bytes_count: u32 = (prefix - 0xb7).into(); + if input_len <= len_bytes_count { + return Result::Err(RLPError::InputTooShort); + } + let string_len_bytes = input.slice(1, len_bytes_count); + let string_len: u32 = string_len_bytes + .from_be_bytes_partial() + .expect('rlp_decode_type_string_len'); + if input_len <= len_bytes_count + string_len { + return Result::Err(RLPError::InputTooShort); + } + + Result::Ok((RLPType::String, 1 + len_bytes_count, string_len)) + } else if prefix < 0xf8 { // Short List + let list_len: u32 = prefix.into() - 0xc0; + if input_len <= list_len { + return Result::Err(RLPError::InputTooShort); + } + Result::Ok((RLPType::List, 1, list_len)) + } else if prefix <= 0xff { // Long List + let len_bytes_count = prefix.into() - 0xf7; + if input.len() <= len_bytes_count { + return Result::Err(RLPError::InputTooShort); + } + let list_len_bytes = input.slice(1, len_bytes_count); + let list_len: u32 = list_len_bytes + .from_be_bytes_partial() + .expect('rlp_decode_type_list_len'); + if input_len <= len_bytes_count + list_len { + return Result::Err(RLPError::InputTooShort); + } + Result::Ok((RLPType::List, 1 + len_bytes_count, list_len)) + } else { + Result::Err(RLPError::InvalidInput) + } + } + + /// RLP encodes a sequence of RLPItems + /// + /// # Arguments + /// * `input` - Span of RLPItems to encode + /// + /// # Returns + /// * `Span` - RLP encoded byte array + /// + /// # Panics + /// * If encoding a long sequence (should not happen in current implementation) + fn encode_sequence(mut input: Span) -> Span { + let mut joined_encodings: Array = Default::default(); + for item in input { + match item { + RLPItem::String(string) => { + joined_encodings.append_span(Self::encode_string(*string)); + }, + RLPItem::List(_) => { panic_with_felt252('List encoding unimplemented') } + } + }; + let len_joined_encodings = joined_encodings.len(); + if len_joined_encodings < 0x38 { + let mut result: Array = array![0xC0 + len_joined_encodings.try_into().unwrap()]; + result.append_span(joined_encodings.span()); + return result.span(); + } else { + // Actual implementation of long list encoding is commented out + // as we should never reach this point in the current implementation + // let bytes_count_len_joined_encodings = len_joined_encodings.bytes_used(); + // let len_joined_encodings: Span = len_joined_encodings.to_bytes(); + // let mut result = array![0xF7 + bytes_count_len_joined_encodings]; + // result.append_span(len_joined_encodings); + // result.append_span(joined_encodings.span()); + // return result.span(); + return panic_with_felt252('Shouldnt encode long sequence'); + } + } + + /// RLP encodes a Span, which is the underlying type used to represent + /// string data in Cairo. + /// + /// # Arguments + /// * `input` - Span to encode + /// + /// # Returns + /// * `Span` - RLP encoded byte array + fn encode_string(input: Span) -> Span { + let len = input.len(); + if len == 0 { + return [0x80].span(); + } else if len == 1 && *input[0] < 0x80 { + return input; + } else if len < 56 { + let mut encoding: Array = Default::default(); + encoding.append(0x80 + len.try_into().unwrap()); + encoding.append_span(input); + return encoding.span(); + } else { + let mut encoding: Array = Default::default(); + let len_as_bytes = len.to_be_bytes(); + let len_bytes_count = len_as_bytes.len(); + let prefix = 0xb7 + len_bytes_count.try_into().unwrap(); + encoding.append(prefix); + encoding.append_span(len_as_bytes); + encoding.append_span(input); + return encoding.span(); + } + } + + /// RLP decodes a rlp encoded byte array + /// as described in https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ + /// + /// # Arguments + /// * `input` - Span of bytes to decode + /// + /// # Returns + /// * `Result, RLPError>` - Span of RLPItems + /// + /// # Errors + /// * `RLPError::InputTooShort` - if the input is too short for a given type + fn decode(input: Span) -> Result, RLPError> { + let mut output: Array = Default::default(); + let input_len = input.len(); + + let (rlp_type, offset, len) = Self::decode_type(input)?; + + if input_len < offset + len { + return Result::Err(RLPError::InputTooShort); + } + + match rlp_type { + RLPType::String => { + if (len == 0) { + output.append(RLPItem::String([].span())); + } else { + output.append(RLPItem::String(input.slice(offset, len))); + } + }, + RLPType::List => { + if len == 0 { + output.append(RLPItem::List([].span())); + } else { + let res = Self::decode(input.slice(offset, len))?; + output.append(RLPItem::List(res)); + } + } + }; + + let total_item_len = len + offset; + if total_item_len < input_len { + output + .append_span( + Self::decode(input.slice(total_item_len, input_len - total_item_len))? + ); + } + + Result::Ok(output.span()) + } +} + +#[generate_trait] +pub impl RLPHelpersImpl of RLPHelpersTrait { + /// Parses a u64 from an RLPItem::String + /// + /// # Returns + /// * `Result` - The parsed u64 value + /// + /// # Errors + /// * `RLPError::NotAString` - if the RLPItem is not a String + fn parse_u64_from_string(self: RLPItem) -> Result { + match self { + RLPItem::String(bytes) => { + // Empty strings means 0 + if bytes.len() == 0 { + return Result::Ok(0); + } + let value = bytes.from_be_bytes_partial().expect('parse_u64_from_string'); + Result::Ok(value) + }, + RLPItem::List(_) => { Result::Err(RLPError::NotAString) } + } + } + + /// Parses a u128 from an RLPItem::String + /// + /// # Returns + /// * `Result` - The parsed u128 value + /// + /// # Errors + /// * `RLPError::NotAString` - if the RLPItem is not a String + fn parse_u128_from_string(self: RLPItem) -> Result { + match self { + RLPItem::String(bytes) => { + // Empty strings means 0 + if bytes.len() == 0 { + return Result::Ok(0); + } + let value = bytes.from_be_bytes_partial().expect('parse_u128_from_string'); + Result::Ok(value) + }, + RLPItem::List(_) => { Result::Err(RLPError::NotAString) } + } + } + + /// Tries to parse an EthAddress from an RLPItem::String + /// + /// # Returns + /// * `Result, RLPError>` - The parsed EthAddress, if present + /// + /// # Errors + /// * `RLPError::NotAString` - if the RLPItem is not a String + /// * `RLPError::FailedParsingAddress` - if the address parsing fails + fn try_parse_address_from_string(self: RLPItem) -> Result, RLPError> { + match self { + RLPItem::String(bytes) => { + if bytes.len() == 0 { + return Result::Ok(Option::None); + } + if bytes.len() == 20 { + let maybe_value = EthAddressExTrait::from_bytes(bytes); + return Result::Ok(maybe_value); + } + return Result::Err(RLPError::FailedParsingAddress); + }, + RLPItem::List(_) => { Result::Err(RLPError::NotAString) } + } + } + + /// Parses a u256 from an RLPItem::String + /// + /// # Returns + /// * `Result` - The parsed u256 value + /// + /// # Errors + /// * `RLPError::NotAString` - if the RLPItem is not a String + fn parse_u256_from_string(self: RLPItem) -> Result { + match self { + RLPItem::String(bytes) => { + // Empty strings means 0 + if bytes.len() == 0 { + return Result::Ok(0); + } + let value = bytes.from_be_bytes_partial().expect('parse_u256_from_string'); + Result::Ok(value) + }, + RLPItem::List(_) => { Result::Err(RLPError::NotAString) } + } + } + + /// Parses bytes from an RLPItem::String + /// + /// # Returns + /// * `Result, RLPError>` - The parsed bytes + /// + /// # Errors + /// * `RLPError::NotAString` - if the RLPItem is not a String + fn parse_bytes_from_string(self: RLPItem) -> Result, RLPError> { + match self { + RLPItem::String(bytes) => { Result::Ok(bytes) }, + RLPItem::List(_) => { Result::Err(RLPError::NotAString) } + } + } + + /// Parses storage keys from an RLPItem + /// + /// # Returns + /// * `Result, RLPError>` - The parsed storage keys + /// + /// # Errors + /// * `RLPError::NotAList` - if the RLPItem is not a List + /// * `RLPError::FailedParsingAddress` - if parsing a storage key fails + fn parse_storage_keys_from_rlp_item(self: RLPItem) -> Result, RLPError> { + match self { + RLPItem::String(_) => { return Result::Err(RLPError::NotAList); }, + RLPItem::List(mut keys) => { + let mut storage_keys: Array = array![]; + let storage_keys: Result, RLPError> = loop { + match keys.pop_front() { + Option::Some(rlp_item) => { + let storage_key = match ((*rlp_item).parse_u256_from_string()) { + Result::Ok(storage_key) => { storage_key }, + Result::Err(err) => { break Result::Err(err); } + }; + + storage_keys.append(storage_key); + }, + Option::None => { break Result::Ok(storage_keys.span()); } + } + }; + + storage_keys + } + } + } + + /// Parses an access list from an RLPItem + /// + /// # Returns + /// * `Result, RLPError>` - The parsed access list + /// + /// # Errors + /// * `RLPError::NotAList` - if the RLPItem is not a List + /// * `RLPError::InputTooShort` - if the input is too short + /// * `RLPError::FailedParsingAccessList` - if parsing the access list fails + fn parse_access_list(self: RLPItem) -> Result, RLPError> { + let mut list_of_accessed_tuples: Span = match self { + RLPItem::String(_) => { return Result::Err(RLPError::NotAList); }, + RLPItem::List(list) => list + }; + + let mut parsed_access_list = array![]; + + // Iterate over the List of [Tuples (RLPString, RLPList)] representing all access list + // entries + let result = loop { + // Get the front Tuple (RLPString, RLPList) + let mut inner_tuple = match list_of_accessed_tuples.pop_front() { + Option::None => { break Result::Ok(parsed_access_list.span()); }, + Option::Some(inner_tuple) => match inner_tuple { + RLPItem::String(_) => { break Result::Err(RLPError::NotAList); }, + RLPItem::List(accessed_tuples) => *accessed_tuples + } + }; + + match inner_tuple.multi_pop_front::<2>() { + Option::None => { break Result::Err(RLPError::InputTooShort); }, + Option::Some(inner_tuple) => { + let [rlp_address, rlp_keys] = (*inner_tuple).unbox(); + let ethereum_address = match rlp_address.try_parse_address_from_string() { + Result::Ok(maybe_eth_address) => { + match (maybe_eth_address) { + Option::Some(eth_address) => { eth_address }, + Option::None => { + break Result::Err(RLPError::FailedParsingAccessList); + } + } + }, + Result::Err(err) => { break Result::Err(err); } + }; + + let storage_keys: Span = + match rlp_keys.parse_storage_keys_from_rlp_item() { + Result::Ok(storage_keys) => storage_keys, + Result::Err(err) => { break Result::Err(err); } + }; + parsed_access_list.append(AccessListItem { ethereum_address, storage_keys }); + } + } + }; + result + } +} +#[cfg(test)] +mod tests { + use core::array::SpanTrait; + use core::option::OptionTrait; + + use core::result::ResultTrait; + use crate::errors::RLPError; + use crate::eth_transaction::eip2930::AccessListItem; + use crate::rlp::{RLPType, RLPTrait, RLPItem, RLPHelpersTrait}; + + // Tests source : + // https://github.com/HerodotusDev/cairo-lib/blob/main/src/encoding/tests/test_rlp.cairo + // https://github.com/ethereum/tests/blob/develop/RLPTests/rlptest.json + + #[test] + fn test_rlp_decode_type_byte() { + let mut arr = array![0x78]; + + let (rlp_type, offset, size) = RLPTrait::decode_type(arr.span()).unwrap(); + + assert(rlp_type == RLPType::String, 'Wrong type'); + assert_eq!(offset, 0); + assert_eq!(size, 1); + } + + #[test] + fn test_rlp_decode_type_short_string() { + let mut arr = array![0x82]; + + let (rlp_type, offset, size) = RLPTrait::decode_type(arr.span()).unwrap(); + + assert(rlp_type == RLPType::String, 'Wrong type'); + assert_eq!(offset, 1); + assert_eq!(size, 2); + } + + #[test] + fn test_rlp_decode_type_long_string() { + let mut arr = array![0xb8, 0x01, 0x02]; + + let (rlp_type, offset, size) = RLPTrait::decode_type(arr.span()).unwrap(); + + assert(rlp_type == RLPType::String, 'Wrong type'); + assert_eq!(offset, 2); + assert_eq!(size, 1); + } + + #[test] + fn test_rlp_decode_type_short_list() { + let mut arr = array![0xc3, 0x01, 0x02, 0x03]; + + let (rlp_type, offset, size) = RLPTrait::decode_type(arr.span()).unwrap(); + + assert(rlp_type == RLPType::List, 'Wrong type'); + assert_eq!(offset, 1); + assert_eq!(size, 3); + } + + #[test] + fn test_rlp_decode_type_long_list() { + let mut arr = array![0xf8, 0x01, 0x00]; + + let (rlp_type, offset, size) = RLPTrait::decode_type(arr.span()).unwrap(); + + assert(rlp_type == RLPType::List, 'Wrong type'); + assert_eq!(offset, 2); + assert_eq!(size, 1); + } + + #[test] + fn test_rlp_decode_type_long_list_len_too_short() { + let mut arr = array![0xf9, 0x01]; + + let res = RLPTrait::decode_type(arr.span()); + + assert(res.is_err(), 'Wrong type'); + assert!(res.unwrap_err() == RLPError::InputTooShort); + } + + #[test] + fn test_rlp_empty() { + let res = RLPTrait::decode(ArrayTrait::new().span()); + + assert(res.is_err(), 'should return an error'); + assert(res.unwrap_err() == RLPError::EmptyInput, 'err != EmptyInput'); + } + + + #[test] + fn test_rlp_encode_string_single_byte_lt_0x80() { + let mut input: Array = Default::default(); + input.append(0x40); + + let res = RLPTrait::encode_string(input.span()); + + assert(res.len() == 1, 'wrong len'); + assert(*res[0] == 0x40, 'wrong encoded value'); + } + + #[test] + fn test_rlp_encode_string_single_byte_ge_0x80() { + let mut input: Array = Default::default(); + input.append(0x80); + + let res = RLPTrait::encode_string(input.span()); + + assert(res.len() == 2, 'wrong len'); + assert(*res[0] == 0x81, 'wrong prefix'); + assert(*res[1] == 0x80, 'wrong encoded value'); + } + + #[test] + fn test_rlp_encode_string_length_between_2_and_55() { + let mut input: Array = Default::default(); + input.append(0x40); + input.append(0x50); + + let res = RLPTrait::encode_string(input.span()); + + assert(res.len() == 3, 'wrong len'); + assert(*res[0] == 0x82, 'wrong prefix'); + assert(*res[1] == 0x40, 'wrong first value'); + assert(*res[2] == 0x50, 'wrong second value'); + } + + #[test] + fn test_rlp_encode_string_length_exactly_56() { + let mut input: Array = Default::default(); + let mut i = 0; + loop { + if i == 56 { + break; + } + input.append(0x60); + i += 1; + }; + + let res = RLPTrait::encode_string(input.span()); + + assert(res.len() == 58, 'wrong len'); + assert(*res[0] == 0xb8, 'wrong prefix'); + assert(*res[1] == 56, 'wrong string length'); + let mut i = 2; + loop { + if i == 58 { + break; + } + assert(*res[i] == 0x60, 'wrong value in sequence'); + i += 1; + }; + } + + #[test] + fn test_rlp_encode_string_length_greater_than_56() { + let mut input: Array = Default::default(); + let mut i = 0; + loop { + if i == 60 { + break; + } + input.append(0x70); + i += 1; + }; + + let res = RLPTrait::encode_string(input.span()); + + assert(res.len() == 62, 'wrong len'); + assert(*res[0] == 0xb8, 'wrong prefix'); + assert(*res[1] == 60, 'wrong length byte'); + let mut i = 2; + loop { + if i == 62 { + break; + } + assert(*res[i] == 0x70, 'wrong value in sequence'); + i += 1; + } + } + + #[test] + fn test_rlp_encode_string_large_bytearray_inputs() { + let mut input: Array = Default::default(); + let mut i = 0; + loop { + if i == 500 { + break; + } + input.append(0x70); + i += 1; + }; + + let res = RLPTrait::encode_string(input.span()); + + assert(res.len() == 503, 'wrong len'); + assert(*res[0] == 0xb9, 'wrong prefix'); + assert(*res[1] == 0x01, 'wrong first length byte'); + assert(*res[2] == 0xF4, 'wrong second length byte'); + let mut i = 3; + loop { + if i == 503 { + break; + } + assert(*res[i] == 0x70, 'wrong value in sequence'); + i += 1; + } + } + + #[test] + fn test_rlp_encode_sequence_empty() { + let res = RLPTrait::encode_sequence([].span()); + + assert(res.len() == 1, 'wrong len'); + assert(*res[0] == 0xC0, 'wrong encoded value'); + } + + #[test] + fn test_rlp_encode_sequence() { + let cat = RLPItem::String([0x63, 0x61, 0x74].span()); + let dog = RLPItem::String([0x64, 0x6f, 0x67].span()); + let input = array![cat, dog]; + + let encoding = RLPTrait::encode_sequence(input.span()); + + let expected = [0xc8, 0x83, 0x63, 0x61, 0x74, 0x83, 0x64, 0x6f, 0x67].span(); + assert(expected == encoding, 'wrong rlp encoding') + } + + #[test] + #[should_panic(expected: ('Shouldnt encode long sequence',))] + fn test_rlp_encode_sequence_long_sequence() { + // encoding of a sequence with more than 55 bytes + let mut lorem_ipsum = RLPItem::String( + [ + 0x4c, + 0x6f, + 0x72, + 0x65, + 0x6d, + 0x20, + 0x69, + 0x70, + 0x73, + 0x75, + 0x6d, + 0x20, + 0x64, + 0x6f, + 0x6c, + 0x6f, + 0x72, + 0x20, + 0x73, + 0x69, + 0x74, + 0x20, + 0x61, + 0x6d, + 0x65, + 0x74, + 0x2c, + 0x20, + 0x63, + 0x6f, + 0x6e, + 0x73, + 0x65, + 0x63, + 0x74, + 0x65, + 0x74, + 0x75, + 0x72, + 0x20, + 0x61, + 0x64, + 0x69, + 0x70, + 0x69, + 0x73, + 0x69, + 0x63, + 0x69, + 0x6e, + 0x67, + 0x20, + 0x65, + 0x6c, + 0x69, + 0x74 + ].span() + ); + let input = [lorem_ipsum].span(); + let encoding = RLPTrait::encode_sequence(input); + + let expected = [ + 0xf8, + 0x3a, + 0xb8, + 0x38, + 0x4c, + 0x6f, + 0x72, + 0x65, + 0x6d, + 0x20, + 0x69, + 0x70, + 0x73, + 0x75, + 0x6d, + 0x20, + 0x64, + 0x6f, + 0x6c, + 0x6f, + 0x72, + 0x20, + 0x73, + 0x69, + 0x74, + 0x20, + 0x61, + 0x6d, + 0x65, + 0x74, + 0x2c, + 0x20, + 0x63, + 0x6f, + 0x6e, + 0x73, + 0x65, + 0x63, + 0x74, + 0x65, + 0x74, + 0x75, + 0x72, + 0x20, + 0x61, + 0x64, + 0x69, + 0x70, + 0x69, + 0x73, + 0x69, + 0x63, + 0x69, + 0x6e, + 0x67, + 0x20, + 0x65, + 0x6c, + 0x69, + 0x74 + ].span(); + assert(expected == encoding, 'wrong rlp encoding') + } + + #[test] + fn test_rlp_decode_string_default_value() { + let mut arr = array![0x80]; + + let rlp_item = RLPTrait::decode(arr.span()).unwrap(); + let expected = RLPItem::String([].span()); + + assert(rlp_item.len() == 1, 'item length not 1'); + assert(*rlp_item[0] == expected, 'default value not 0'); + } + + #[test] + fn test_rlp_decode_string() { + let mut i = 0; + loop { + if i == 0x80 { + break; + } + let mut arr = ArrayTrait::new(); + arr.append(i); + + let res = RLPTrait::decode(arr.span()).unwrap(); + + assert(res == [RLPItem::String(arr.span())].span(), 'Wrong value'); + + i += 1; + }; + } + + #[test] + fn test_rlp_decode_short_string() { + let mut arr = array![ + 0x9b, + 0x5a, + 0x80, + 0x6c, + 0xf6, + 0x34, + 0xc0, + 0x39, + 0x8d, + 0x8f, + 0x2d, + 0x89, + 0xfd, + 0x49, + 0xa9, + 0x1e, + 0xf3, + 0x3d, + 0xa4, + 0x74, + 0xcd, + 0x84, + 0x94, + 0xbb, + 0xa8, + 0xda, + 0x3b, + 0xf7 + ]; + + let res = RLPTrait::decode(arr.span()).unwrap(); + + // Remove the byte representing the data type + arr.pop_front().expect('pop_front failed'); + let expected_item = [RLPItem::String(arr.span())].span(); + + assert(res == expected_item, 'Wrong value'); + } + + #[test] + fn test_rlp_decode_short_string_input_too_short() { + let mut arr = array![ + 0x9b, + 0x5a, + 0x80, + 0x6c, + 0xf6, + 0x34, + 0xc0, + 0x39, + 0x8d, + 0x8f, + 0x2d, + 0x89, + 0xfd, + 0x49, + 0xa9, + 0x1e, + 0xf3, + 0x3d, + 0xa4, + 0x74, + 0xcd, + 0x84, + 0x94, + 0xbb, + 0xa8, + 0xda, + 0x3b + ]; + + let res = RLPTrait::decode(arr.span()); + assert(res.is_err(), 'should return an RLPError'); + assert!(res.unwrap_err() == RLPError::InputTooShort); + } + + #[test] + fn test_rlp_decode_long_string_with_payload_len_on_1_byte() { + let mut arr = array![ + 0xb8, + 0x3c, + 0xf7, + 0xa1, + 0x7e, + 0xf9, + 0x59, + 0xd4, + 0x88, + 0x38, + 0x8d, + 0xdc, + 0x34, + 0x7b, + 0x3a, + 0x10, + 0xdd, + 0x85, + 0x43, + 0x1d, + 0x0c, + 0x37, + 0x98, + 0x6a, + 0x63, + 0xbd, + 0x18, + 0xba, + 0xa3, + 0x8d, + 0xb1, + 0xa4, + 0x81, + 0x6f, + 0x24, + 0xde, + 0xc3, + 0xec, + 0x16, + 0x6e, + 0xb3, + 0xb2, + 0xac, + 0xc4, + 0xc4, + 0xf7, + 0x79, + 0x04, + 0xba, + 0x76, + 0x3c, + 0x67, + 0xc6, + 0xd0, + 0x53, + 0xda, + 0xea, + 0x10, + 0x86, + 0x19, + 0x7d, + 0xd9 + ]; + + let res = RLPTrait::decode(arr.span()).unwrap(); + + // Remove the bytes representing the data type and their length + arr.pop_front().expect('pop_front failed'); + arr.pop_front().expect('pop_front failed'); + let expected_item = [RLPItem::String(arr.span())].span(); + + assert(res == expected_item, 'Wrong value'); + } + + #[test] + fn test_rlp_decode_long_string_with_input_too_short() { + let mut arr = array![ + 0xb8, + 0x3c, + 0xf7, + 0xa1, + 0x7e, + 0xf9, + 0x59, + 0xd4, + 0x88, + 0x38, + 0x8d, + 0xdc, + 0x34, + 0x7b, + 0x3a, + 0x10, + 0xdd, + 0x85, + 0x43, + 0x1d, + 0x0c, + 0x37, + 0x98, + 0x6a, + 0x63, + 0xbd, + 0x18, + 0xba, + 0xa3, + 0x8d, + 0xb1, + 0xa4, + 0x81, + 0x6f, + 0x24, + 0xde, + 0xc3, + 0xec, + 0x16, + 0x6e, + 0xb3, + 0xb2, + 0xac, + 0xc4, + 0xc4, + 0xf7, + 0x79, + 0x04, + 0xba, + 0x76, + 0x3c, + 0x67, + 0xc6, + 0xd0, + 0x53, + 0xda, + 0xea, + 0x10, + 0x86, + 0x19, + ]; + + let res = RLPTrait::decode(arr.span()); + assert(res.is_err(), 'should return an RLPError'); + assert!(res.unwrap_err() == RLPError::InputTooShort); + } + + #[test] + fn test_rlp_decode_long_string_with_payload_len_on_2_bytes() { + let mut arr = array![ + 0xb9, + 0x01, + 0x02, + 0xf7, + 0xa1, + 0x7e, + 0xf9, + 0x59, + 0xd4, + 0x88, + 0x38, + 0x8d, + 0xdc, + 0x34, + 0x7b, + 0x3a, + 0x10, + 0xdd, + 0x85, + 0x43, + 0x1d, + 0x0c, + 0x37, + 0x98, + 0x6a, + 0x63, + 0xbd, + 0x18, + 0xba, + 0xa3, + 0x8d, + 0xb1, + 0xa4, + 0x81, + 0x6f, + 0x24, + 0xde, + 0xc3, + 0xec, + 0x16, + 0x6e, + 0xb3, + 0xb2, + 0xac, + 0xc4, + 0xc4, + 0xf7, + 0x79, + 0x04, + 0xba, + 0x76, + 0x3c, + 0x67, + 0xc6, + 0xd0, + 0x53, + 0xda, + 0xea, + 0x10, + 0x86, + 0x19, + 0x7d, + 0xd9, + 0x33, + 0x58, + 0x47, + 0x69, + 0x34, + 0x76, + 0x89, + 0x43, + 0x67, + 0x93, + 0x45, + 0x76, + 0x87, + 0x34, + 0x95, + 0x67, + 0x89, + 0x34, + 0x36, + 0x43, + 0x86, + 0x79, + 0x43, + 0x63, + 0x34, + 0x78, + 0x63, + 0x49, + 0x58, + 0x67, + 0x83, + 0x59, + 0x64, + 0x56, + 0x37, + 0x93, + 0x74, + 0x58, + 0x69, + 0x69, + 0x43, + 0x67, + 0x39, + 0x48, + 0x67, + 0x98, + 0x45, + 0x63, + 0x89, + 0x45, + 0x67, + 0x94, + 0x37, + 0x63, + 0x04, + 0x56, + 0x40, + 0x39, + 0x68, + 0x43, + 0x08, + 0x68, + 0x40, + 0x65, + 0x03, + 0x46, + 0x80, + 0x93, + 0x48, + 0x64, + 0x95, + 0x36, + 0x87, + 0x39, + 0x84, + 0x56, + 0x73, + 0x76, + 0x89, + 0x34, + 0x95, + 0x86, + 0x73, + 0x65, + 0x40, + 0x93, + 0x60, + 0x98, + 0x34, + 0x56, + 0x83, + 0x04, + 0x56, + 0x80, + 0x36, + 0x08, + 0x59, + 0x68, + 0x45, + 0x06, + 0x83, + 0x06, + 0x68, + 0x40, + 0x59, + 0x68, + 0x40, + 0x65, + 0x84, + 0x03, + 0x68, + 0x30, + 0x48, + 0x65, + 0x03, + 0x46, + 0x83, + 0x49, + 0x57, + 0x68, + 0x95, + 0x79, + 0x68, + 0x34, + 0x76, + 0x83, + 0x74, + 0x69, + 0x87, + 0x43, + 0x59, + 0x63, + 0x84, + 0x75, + 0x63, + 0x98, + 0x47, + 0x56, + 0x34, + 0x86, + 0x73, + 0x94, + 0x87, + 0x65, + 0x43, + 0x98, + 0x67, + 0x34, + 0x96, + 0x79, + 0x34, + 0x86, + 0x57, + 0x93, + 0x48, + 0x57, + 0x69, + 0x34, + 0x87, + 0x56, + 0x89, + 0x34, + 0x57, + 0x68, + 0x73, + 0x49, + 0x56, + 0x53, + 0x79, + 0x43, + 0x95, + 0x67, + 0x34, + 0x96, + 0x79, + 0x38, + 0x47, + 0x63, + 0x94, + 0x65, + 0x37, + 0x89, + 0x63, + 0x53, + 0x45, + 0x68, + 0x79, + 0x88, + 0x97, + 0x68, + 0x87, + 0x68, + 0x68, + 0x68, + 0x76, + 0x99, + 0x87, + 0x60 + ]; + + let res = RLPTrait::decode(arr.span()).unwrap(); + + // Remove the bytes representing the data type and their length + arr.pop_front().expect('pop_front failed'); + arr.pop_front().expect('pop_front failed'); + arr.pop_front().expect('pop_front failed'); + let expected_item = [RLPItem::String(arr.span())].span(); + + assert(res == expected_item, 'Wrong value'); + } + + + #[test] + fn test_rlp_decode_long_string_with_payload_len_too_short() { + let mut arr = array![0xb9, 0x01,]; + + let res = RLPTrait::decode(arr.span()); + assert(res.is_err(), 'should return an RLPError'); + assert!(res.unwrap_err() == RLPError::InputTooShort); + } + + #[test] + fn test_rlp_decode_short_list() { + let mut arr = array![0xc9, 0x83, 0x35, 0x35, 0x35, 0x42, 0x83, 0x45, 0x38, 0x92]; + let res = RLPTrait::decode(arr.span()).unwrap(); + + let mut expected_0 = RLPItem::String([0x35, 0x35, 0x35].span()); + let mut expected_1 = RLPItem::String([0x42].span()); + let mut expected_2 = RLPItem::String([0x45, 0x38, 0x92].span()); + + let expected_list = RLPItem::List([expected_0, expected_1, expected_2].span()); + + assert(res == [expected_list].span(), 'Wrong value'); + } + + #[test] + fn test_rlp_decode_short_nested_list() { + let mut arr = array![0xc7, 0xc0, 0xc1, 0xc0, 0xc3, 0xc0, 0xc1, 0xc0]; + let res = RLPTrait::decode(arr.span()).unwrap(); + + let mut expected_0 = RLPItem::List([].span()); + let mut expected_1 = RLPItem::List([expected_0].span()); + let mut expected_2 = RLPItem::List([expected_0, expected_1].span()); + + let expected = RLPItem::List([expected_0, expected_1, expected_2].span()); + + assert(res == [expected].span(), 'Wrong value'); + } + + #[test] + fn test_rlp_decode_multi_list() { + let mut arr = array![0xc6, 0x82, 0x7a, 0x77, 0xc1, 0x04, 0x01,]; + + let res = RLPTrait::decode(arr.span()).unwrap(); + + let mut expected_0 = RLPItem::String([0x7a, 0x77].span()); + let mut expected_1 = RLPItem::String([0x04].span()); + let mut expected_1 = RLPItem::List([expected_1].span()); + let mut expected_2 = RLPItem::String([0x01].span()); + let mut expected = RLPItem::List([expected_0, expected_1, expected_2].span()); + + assert(res == [expected].span(), 'Wrong value'); + } + + #[test] + fn test_rlp_decode_short_list_with_input_too_short() { + let mut arr = array![0xc9, 0x83, 0x35, 0x35, 0x89, 0x42, 0x83, 0x45, 0x38]; + + let res = RLPTrait::decode(arr.span()); + assert(res.is_err(), 'should return an RLPError'); + assert!(res.unwrap_err() == RLPError::InputTooShort); + } + + #[test] + fn test_rlp_decode_long_list() { + let mut arr = array![ + 0xf9, + 0x02, + 0x11, + 0xa0, + 0x77, + 0x70, + 0xcf, + 0x09, + 0xb5, + 0x06, + 0x7a, + 0x1b, + 0x35, + 0xdf, + 0x62, + 0xa9, + 0x24, + 0x89, + 0x81, + 0x75, + 0xce, + 0xae, + 0xec, + 0xad, + 0x1f, + 0x68, + 0xcd, + 0xb4, + 0xa8, + 0x44, + 0x40, + 0x0c, + 0x73, + 0xc1, + 0x4a, + 0xf4, + 0xa0, + 0x1e, + 0xa3, + 0x85, + 0xd0, + 0x5a, + 0xb2, + 0x61, + 0x46, + 0x6d, + 0x5c, + 0x04, + 0x87, + 0xfe, + 0x68, + 0x45, + 0x34, + 0xc1, + 0x9f, + 0x1a, + 0x4b, + 0x5c, + 0x4b, + 0x18, + 0xdc, + 0x1a, + 0x36, + 0x35, + 0x60, + 0x02, + 0x50, + 0x71, + 0xb4, + 0xa0, + 0x2c, + 0x4c, + 0x04, + 0xce, + 0x35, + 0x40, + 0xd3, + 0xd1, + 0x46, + 0x18, + 0x72, + 0x30, + 0x3c, + 0x53, + 0xa5, + 0xe5, + 0x66, + 0x83, + 0xc1, + 0x30, + 0x4f, + 0x8d, + 0x36, + 0xa8, + 0x80, + 0x0c, + 0x6a, + 0xf5, + 0xfa, + 0x3f, + 0xcd, + 0xee, + 0xa0, + 0xa9, + 0xdc, + 0x77, + 0x8d, + 0xc5, + 0x4b, + 0x7d, + 0xd3, + 0xc4, + 0x82, + 0x22, + 0xe7, + 0x39, + 0xd1, + 0x61, + 0xfe, + 0xb0, + 0xc0, + 0xee, + 0xce, + 0xb2, + 0xdc, + 0xd5, + 0x17, + 0x37, + 0xf0, + 0x5b, + 0x8e, + 0x37, + 0xa6, + 0x38, + 0x51, + 0xa0, + 0xa9, + 0x5f, + 0x4d, + 0x55, + 0x56, + 0xdf, + 0x62, + 0xdd, + 0xc2, + 0x62, + 0x99, + 0x04, + 0x97, + 0xae, + 0x56, + 0x9b, + 0xcd, + 0x8e, + 0xfd, + 0xda, + 0x7b, + 0x20, + 0x07, + 0x93, + 0xf8, + 0xd3, + 0xde, + 0x4c, + 0xdb, + 0x97, + 0x18, + 0xd7, + 0xa0, + 0x39, + 0xd4, + 0x06, + 0x6d, + 0x14, + 0x38, + 0x22, + 0x6e, + 0xaf, + 0x4a, + 0xc9, + 0xe9, + 0x43, + 0xa8, + 0x74, + 0xa9, + 0xa9, + 0xc2, + 0x5f, + 0xb0, + 0xd8, + 0x1d, + 0xb9, + 0x86, + 0x1d, + 0x8c, + 0x13, + 0x36, + 0xb3, + 0xe2, + 0x03, + 0x4c, + 0xa0, + 0x7a, + 0xcc, + 0x7c, + 0x63, + 0xb4, + 0x6a, + 0xa4, + 0x18, + 0xb3, + 0xc9, + 0xa0, + 0x41, + 0xa1, + 0x25, + 0x6b, + 0xcb, + 0x73, + 0x61, + 0x31, + 0x6b, + 0x39, + 0x7a, + 0xda, + 0x5a, + 0x88, + 0x67, + 0x49, + 0x1b, + 0xbb, + 0x13, + 0x01, + 0x30, + 0xa0, + 0x15, + 0x35, + 0x8a, + 0x81, + 0x25, + 0x2e, + 0xc4, + 0x93, + 0x71, + 0x13, + 0xfe, + 0x36, + 0xc7, + 0x80, + 0x46, + 0xb7, + 0x11, + 0xfb, + 0xa1, + 0x97, + 0x34, + 0x91, + 0xbb, + 0x29, + 0x18, + 0x7a, + 0x00, + 0x78, + 0x5f, + 0xf8, + 0x52, + 0xae, + 0xa0, + 0x68, + 0x91, + 0x42, + 0xd3, + 0x16, + 0xab, + 0xfa, + 0xa7, + 0x1c, + 0x8b, + 0xce, + 0xdf, + 0x49, + 0x20, + 0x1d, + 0xdb, + 0xb2, + 0x10, + 0x4e, + 0x25, + 0x0a, + 0xdc, + 0x90, + 0xc4, + 0xe8, + 0x56, + 0x22, + 0x1f, + 0x53, + 0x4a, + 0x96, + 0x58, + 0xa0, + 0xdc, + 0x36, + 0x50, + 0x99, + 0x25, + 0x34, + 0xfd, + 0xa8, + 0xa3, + 0x14, + 0xa7, + 0xdb, + 0xb0, + 0xae, + 0x3b, + 0xa8, + 0xc7, + 0x9d, + 0xb5, + 0x55, + 0x0c, + 0x69, + 0xce, + 0x2a, + 0x24, + 0x60, + 0xc0, + 0x07, + 0xad, + 0xc4, + 0xc1, + 0xa3, + 0xa0, + 0x20, + 0xb0, + 0x68, + 0x3b, + 0x66, + 0x55, + 0xb0, + 0x05, + 0x9e, + 0xe1, + 0x03, + 0xd0, + 0x4e, + 0x4b, + 0x50, + 0x6b, + 0xcb, + 0xc1, + 0x39, + 0x00, + 0x63, + 0x92, + 0xb7, + 0xda, + 0xb1, + 0x11, + 0x78, + 0xc2, + 0x66, + 0x03, + 0x42, + 0xe7, + 0xa0, + 0x8e, + 0xed, + 0xeb, + 0x45, + 0xfb, + 0x63, + 0x0f, + 0x1c, + 0xd9, + 0x97, + 0x36, + 0xeb, + 0x18, + 0x57, + 0x22, + 0x17, + 0xcb, + 0xc6, + 0xd5, + 0xf3, + 0x15, + 0xb7, + 0x1b, + 0xe2, + 0x03, + 0xb0, + 0x3c, + 0xe8, + 0xd9, + 0x9b, + 0x26, + 0x14, + 0xa0, + 0x79, + 0x23, + 0xa3, + 0x3d, + 0xf6, + 0x5a, + 0x98, + 0x6f, + 0xd5, + 0xe7, + 0xf9, + 0xe6, + 0xe4, + 0xc2, + 0xb9, + 0x69, + 0x73, + 0x6b, + 0x08, + 0x94, + 0x4e, + 0xbe, + 0x99, + 0x39, + 0x4a, + 0x86, + 0x14, + 0x61, + 0x2f, + 0xe6, + 0x09, + 0xf3, + 0xa0, + 0x65, + 0x34, + 0xd7, + 0xd0, + 0x1a, + 0x20, + 0x71, + 0x4a, + 0xa4, + 0xfb, + 0x2a, + 0x55, + 0xb9, + 0x46, + 0xce, + 0x64, + 0xc3, + 0x22, + 0x2d, + 0xff, + 0xad, + 0x2a, + 0xa2, + 0xd1, + 0x8a, + 0x92, + 0x34, + 0x73, + 0xc9, + 0x2a, + 0xb1, + 0xfd, + 0xa0, + 0xbf, + 0xf9, + 0xc2, + 0x8b, + 0xfe, + 0xb8, + 0xbf, + 0x2d, + 0xa9, + 0xb6, + 0x18, + 0xc8, + 0xc3, + 0xb0, + 0x6f, + 0xe8, + 0x0c, + 0xb1, + 0xc0, + 0xbd, + 0x14, + 0x47, + 0x38, + 0xf7, + 0xc4, + 0x21, + 0x61, + 0xff, + 0x29, + 0xe2, + 0x50, + 0x2f, + 0xa0, + 0x7f, + 0x14, + 0x61, + 0x69, + 0x3c, + 0x70, + 0x4e, + 0xa5, + 0x02, + 0x1b, + 0xbb, + 0xa3, + 0x5e, + 0x72, + 0xc5, + 0x02, + 0xf6, + 0x43, + 0x9e, + 0x45, + 0x8f, + 0x98, + 0x24, + 0x2e, + 0xd0, + 0x37, + 0x48, + 0xea, + 0x8f, + 0xe2, + 0xb3, + 0x5f, + 0x80 + ]; + let res = RLPTrait::decode(arr.span()).unwrap(); + + let mut expected_0 = RLPItem::String( + [ + 0x77, + 0x70, + 0xcf, + 0x09, + 0xb5, + 0x06, + 0x7a, + 0x1b, + 0x35, + 0xdf, + 0x62, + 0xa9, + 0x24, + 0x89, + 0x81, + 0x75, + 0xce, + 0xae, + 0xec, + 0xad, + 0x1f, + 0x68, + 0xcd, + 0xb4, + 0xa8, + 0x44, + 0x40, + 0x0c, + 0x73, + 0xc1, + 0x4a, + 0xf4 + ].span() + ); + let mut expected_1 = RLPItem::String( + [ + 0x1e, + 0xa3, + 0x85, + 0xd0, + 0x5a, + 0xb2, + 0x61, + 0x46, + 0x6d, + 0x5c, + 0x04, + 0x87, + 0xfe, + 0x68, + 0x45, + 0x34, + 0xc1, + 0x9f, + 0x1a, + 0x4b, + 0x5c, + 0x4b, + 0x18, + 0xdc, + 0x1a, + 0x36, + 0x35, + 0x60, + 0x02, + 0x50, + 0x71, + 0xb4 + ].span() + ); + let mut expected_2 = RLPItem::String( + [ + 0x2c, + 0x4c, + 0x04, + 0xce, + 0x35, + 0x40, + 0xd3, + 0xd1, + 0x46, + 0x18, + 0x72, + 0x30, + 0x3c, + 0x53, + 0xa5, + 0xe5, + 0x66, + 0x83, + 0xc1, + 0x30, + 0x4f, + 0x8d, + 0x36, + 0xa8, + 0x80, + 0x0c, + 0x6a, + 0xf5, + 0xfa, + 0x3f, + 0xcd, + 0xee + ].span() + ); + let mut expected_3 = RLPItem::String( + [ + 0xa9, + 0xdc, + 0x77, + 0x8d, + 0xc5, + 0x4b, + 0x7d, + 0xd3, + 0xc4, + 0x82, + 0x22, + 0xe7, + 0x39, + 0xd1, + 0x61, + 0xfe, + 0xb0, + 0xc0, + 0xee, + 0xce, + 0xb2, + 0xdc, + 0xd5, + 0x17, + 0x37, + 0xf0, + 0x5b, + 0x8e, + 0x37, + 0xa6, + 0x38, + 0x51 + ].span() + ); + let mut expected_4 = RLPItem::String( + [ + 0xa9, + 0x5f, + 0x4d, + 0x55, + 0x56, + 0xdf, + 0x62, + 0xdd, + 0xc2, + 0x62, + 0x99, + 0x04, + 0x97, + 0xae, + 0x56, + 0x9b, + 0xcd, + 0x8e, + 0xfd, + 0xda, + 0x7b, + 0x20, + 0x07, + 0x93, + 0xf8, + 0xd3, + 0xde, + 0x4c, + 0xdb, + 0x97, + 0x18, + 0xd7 + ].span() + ); + let mut expected_5 = RLPItem::String( + [ + 0x39, + 0xd4, + 0x06, + 0x6d, + 0x14, + 0x38, + 0x22, + 0x6e, + 0xaf, + 0x4a, + 0xc9, + 0xe9, + 0x43, + 0xa8, + 0x74, + 0xa9, + 0xa9, + 0xc2, + 0x5f, + 0xb0, + 0xd8, + 0x1d, + 0xb9, + 0x86, + 0x1d, + 0x8c, + 0x13, + 0x36, + 0xb3, + 0xe2, + 0x03, + 0x4c + ].span() + ); + let mut expected_6 = RLPItem::String( + [ + 0x7a, + 0xcc, + 0x7c, + 0x63, + 0xb4, + 0x6a, + 0xa4, + 0x18, + 0xb3, + 0xc9, + 0xa0, + 0x41, + 0xa1, + 0x25, + 0x6b, + 0xcb, + 0x73, + 0x61, + 0x31, + 0x6b, + 0x39, + 0x7a, + 0xda, + 0x5a, + 0x88, + 0x67, + 0x49, + 0x1b, + 0xbb, + 0x13, + 0x01, + 0x30 + ].span() + ); + let mut expected_7 = RLPItem::String( + [ + 0x15, + 0x35, + 0x8a, + 0x81, + 0x25, + 0x2e, + 0xc4, + 0x93, + 0x71, + 0x13, + 0xfe, + 0x36, + 0xc7, + 0x80, + 0x46, + 0xb7, + 0x11, + 0xfb, + 0xa1, + 0x97, + 0x34, + 0x91, + 0xbb, + 0x29, + 0x18, + 0x7a, + 0x00, + 0x78, + 0x5f, + 0xf8, + 0x52, + 0xae + ].span() + ); + let mut expected_8 = RLPItem::String( + [ + 0x68, + 0x91, + 0x42, + 0xd3, + 0x16, + 0xab, + 0xfa, + 0xa7, + 0x1c, + 0x8b, + 0xce, + 0xdf, + 0x49, + 0x20, + 0x1d, + 0xdb, + 0xb2, + 0x10, + 0x4e, + 0x25, + 0x0a, + 0xdc, + 0x90, + 0xc4, + 0xe8, + 0x56, + 0x22, + 0x1f, + 0x53, + 0x4a, + 0x96, + 0x58 + ].span() + ); + let mut expected_9 = RLPItem::String( + [ + 0xdc, + 0x36, + 0x50, + 0x99, + 0x25, + 0x34, + 0xfd, + 0xa8, + 0xa3, + 0x14, + 0xa7, + 0xdb, + 0xb0, + 0xae, + 0x3b, + 0xa8, + 0xc7, + 0x9d, + 0xb5, + 0x55, + 0x0c, + 0x69, + 0xce, + 0x2a, + 0x24, + 0x60, + 0xc0, + 0x07, + 0xad, + 0xc4, + 0xc1, + 0xa3 + ].span() + ); + let mut expected_10 = RLPItem::String( + [ + 0x20, + 0xb0, + 0x68, + 0x3b, + 0x66, + 0x55, + 0xb0, + 0x05, + 0x9e, + 0xe1, + 0x03, + 0xd0, + 0x4e, + 0x4b, + 0x50, + 0x6b, + 0xcb, + 0xc1, + 0x39, + 0x00, + 0x63, + 0x92, + 0xb7, + 0xda, + 0xb1, + 0x11, + 0x78, + 0xc2, + 0x66, + 0x03, + 0x42, + 0xe7 + ].span() + ); + let mut expected_11 = RLPItem::String( + [ + 0x8e, + 0xed, + 0xeb, + 0x45, + 0xfb, + 0x63, + 0x0f, + 0x1c, + 0xd9, + 0x97, + 0x36, + 0xeb, + 0x18, + 0x57, + 0x22, + 0x17, + 0xcb, + 0xc6, + 0xd5, + 0xf3, + 0x15, + 0xb7, + 0x1b, + 0xe2, + 0x03, + 0xb0, + 0x3c, + 0xe8, + 0xd9, + 0x9b, + 0x26, + 0x14 + ].span() + ); + let mut expected_12 = RLPItem::String( + [ + 0x79, + 0x23, + 0xa3, + 0x3d, + 0xf6, + 0x5a, + 0x98, + 0x6f, + 0xd5, + 0xe7, + 0xf9, + 0xe6, + 0xe4, + 0xc2, + 0xb9, + 0x69, + 0x73, + 0x6b, + 0x08, + 0x94, + 0x4e, + 0xbe, + 0x99, + 0x39, + 0x4a, + 0x86, + 0x14, + 0x61, + 0x2f, + 0xe6, + 0x09, + 0xf3 + ].span() + ); + let mut expected_13 = RLPItem::String( + [ + 0x65, + 0x34, + 0xd7, + 0xd0, + 0x1a, + 0x20, + 0x71, + 0x4a, + 0xa4, + 0xfb, + 0x2a, + 0x55, + 0xb9, + 0x46, + 0xce, + 0x64, + 0xc3, + 0x22, + 0x2d, + 0xff, + 0xad, + 0x2a, + 0xa2, + 0xd1, + 0x8a, + 0x92, + 0x34, + 0x73, + 0xc9, + 0x2a, + 0xb1, + 0xfd + ].span() + ); + let mut expected_14 = RLPItem::String( + [ + 0xbf, + 0xf9, + 0xc2, + 0x8b, + 0xfe, + 0xb8, + 0xbf, + 0x2d, + 0xa9, + 0xb6, + 0x18, + 0xc8, + 0xc3, + 0xb0, + 0x6f, + 0xe8, + 0x0c, + 0xb1, + 0xc0, + 0xbd, + 0x14, + 0x47, + 0x38, + 0xf7, + 0xc4, + 0x21, + 0x61, + 0xff, + 0x29, + 0xe2, + 0x50, + 0x2f + ].span() + ); + let mut expected_15 = RLPItem::String( + [ + 0x7f, + 0x14, + 0x61, + 0x69, + 0x3c, + 0x70, + 0x4e, + 0xa5, + 0x02, + 0x1b, + 0xbb, + 0xa3, + 0x5e, + 0x72, + 0xc5, + 0x02, + 0xf6, + 0x43, + 0x9e, + 0x45, + 0x8f, + 0x98, + 0x24, + 0x2e, + 0xd0, + 0x37, + 0x48, + 0xea, + 0x8f, + 0xe2, + 0xb3, + 0x5f + ].span() + ); + + let mut expected_16 = RLPItem::String([].span()); + + let mut expected = array![ + expected_0, + expected_1, + expected_2, + expected_3, + expected_4, + expected_5, + expected_6, + expected_7, + expected_8, + expected_9, + expected_10, + expected_11, + expected_12, + expected_13, + expected_14, + expected_15, + expected_16 + ]; + + let expected_item = RLPItem::List(expected.span()); + + assert(res == [expected_item].span(), 'Wrong value'); + } + + #[test] + fn test_rlp_decode_long_list_with_input_too_short() { + let mut arr = array![ + 0xf9, + 0x02, + 0x11, + 0xa0, + 0x77, + 0x70, + 0xcf, + 0x09, + 0xb5, + 0x06, + 0x7a, + 0x1b, + 0x35, + 0xdf, + 0x62, + 0xa9, + 0x24, + 0x89, + 0x81, + 0x75, + 0xce, + 0xae, + 0xec, + 0xad, + 0x1f, + 0x68, + 0xcd, + 0xb4 + ]; + + let res = RLPTrait::decode(arr.span()); + assert(res.is_err(), 'should return an RLPError'); + assert!(res.unwrap_err() == RLPError::InputTooShort); + } + + #[test] + fn test_rlp_decode_long_list_with_len_too_short() { + let mut arr = array![0xf9, 0x02,]; + + let res = RLPTrait::decode(arr.span()); + assert(res.is_err(), 'should return an RLPError'); + assert!(res.unwrap_err() == RLPError::InputTooShort); + } + + #[test] + fn test_rlp_item_parse_access_list_empty() { + let rlp_encoded_access_list: Span = [0xc0].span(); + let decoded_data = RLPTrait::decode(rlp_encoded_access_list).unwrap(); + assert_eq!(decoded_data.len(), 1); + + let rlp_item = *decoded_data[0]; + let res = rlp_item.parse_access_list().unwrap(); + assert_eq!(res.len(), 0); + } + + #[test] + fn test_rlp_item_parse_access_list() { + // (('0x0000000000000000000000000000000000000001', + // ('0x0100000000000000000000000000000000000000000000000000000000000000',)), + // ('0x0000000000000000000000000000000000000002', + // ('0x0100000000000000000000000000000000000000000000000000000000000000', + // '0x0200000000000000000000000000000000000000000000000000000000000000')), + // ('0x0000000000000000000000000000000000000003', ())) + let rlp_encoded_access_list: Span = [ + 248, + 170, + 247, + 148, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 225, + 160, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 248, + 89, + 148, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 248, + 66, + 160, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 160, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 214, + 148, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 192 + ].span(); + let decoded_data = RLPTrait::decode(rlp_encoded_access_list).unwrap(); + assert_eq!(decoded_data.len(), 1); + + let rlp_item = *decoded_data[0]; + + let expected_access_list = [ + AccessListItem { + ethereum_address: 0x0000000000000000000000000000000000000001.try_into().unwrap(), + storage_keys: [ + 0x0100000000000000000000000000000000000000000000000000000000000000 + ].span() + }, + AccessListItem { + ethereum_address: 0x0000000000000000000000000000000000000002.try_into().unwrap(), + storage_keys: [ + 0x0100000000000000000000000000000000000000000000000000000000000000, + 0x0200000000000000000000000000000000000000000000000000000000000000 + ].span() + }, + AccessListItem { + ethereum_address: 0x0000000000000000000000000000000000000003.try_into().unwrap(), + storage_keys: [].span() + } + ].span(); + + let res = rlp_item.parse_access_list().unwrap(); + assert!(res == expected_access_list); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/serialization.cairo b/cairo/kakarot-ssj/crates/utils/src/serialization.cairo new file mode 100644 index 000000000..371f9dd1c --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/serialization.cairo @@ -0,0 +1,301 @@ +use core::starknet::secp256_trait::{Signature}; +use crate::eth_transaction::tx_type::TxType; +use crate::traits::BoolIntoNumeric; + +/// Deserializes a signature from a span of felt252 values. +/// +/// # Arguments +/// +/// * `signature` - A span of felt252 values representing the signature components. +/// * `chain_id` - The chain ID used for EIP-155 signature recovery. +/// +/// # Returns +/// +/// * `Option` - The deserialized signature if successful, or None if deserialization +/// fails. +pub fn deserialize_signature(signature: Span, chain_id: u64) -> Option { + let r_low: u128 = (*signature.at(0)).try_into()?; + let r_high: u128 = (*signature.at(1)).try_into()?; + + let s_low: u128 = (*signature.at(2)).try_into()?; + let s_high: u128 = (*signature.at(3)).try_into()?; + + let v: u128 = (*signature.at(4)).try_into()?; + + let odd_y_parity = if v == 0 { + false + } else if v == 1 { + true + } else { + compute_y_parity(v, chain_id)? + }; + + Option::Some( + Signature { + r: u256 { low: r_low, high: r_high }, + s: u256 { low: s_low, high: s_high }, + y_parity: odd_y_parity, + } + ) +} + +/// Computes the y-parity value for EIP-155 signature recovery. +/// +/// # Arguments +/// +/// * `v` - The v value from the signature. +/// * `chain_id` - The chain ID used for EIP-155 signature recovery. +/// +/// # Returns +/// +/// * `Option` - The computed y-parity value if valid, or None if invalid. +fn compute_y_parity(v: u128, chain_id: u64) -> Option { + let y_parity = v - (chain_id.into() * 2 + 35); + if (y_parity == 0 || y_parity == 1) { + return Option::Some(y_parity == 1); + } + + return Option::None; +} + +/// Serializes a transaction signature into an array of felt252 values. +/// +/// # Arguments +/// +/// * `sig` - The signature to serialize. +/// * `tx_type` - The transaction type (Legacy, EIP-2930, or EIP-1559). +/// * `chain_id` - The chain ID used for EIP-155 signature recovery. +/// +/// # Returns +/// +/// * `Array` - The serialized signature as an array of felt252 values. +pub fn serialize_transaction_signature( + sig: Signature, tx_type: TxType, chain_id: u64 +) -> Array { + let mut res: Array = array![ + sig.r.low.into(), sig.r.high.into(), sig.s.low.into(), sig.s.high.into() + ]; + + let value = match tx_type { + TxType::Legacy(_) => { sig.y_parity.into() + 2 * chain_id + 35 }, + TxType::Eip2930(_) | TxType::Eip1559(_) => { sig.y_parity.into() } + }; + + res.append(value.into()); + res +} + +/// Deserializes a span of felt252 values into an array of bytes. +/// +/// # Arguments +/// +/// * `self` - A span of felt252 values to deserialize. +/// +/// # Returns +/// +/// * `Option>` - The deserialized bytes if successful, or None if deserialization fails. +pub fn deserialize_bytes(self: Span) -> Option> { + let mut bytes: Array = Default::default(); + + for item in self { + let v: Option = (*item).try_into(); + + match v { + Option::Some(v) => { bytes.append(v); }, + Option::None => { break; } + } + }; + + // it means there was an error in the above loop + if (bytes.len() != self.len()) { + Option::None + } else { + Option::Some(bytes) + } +} + +/// Serializes a span of bytes into an array of felt252 values. +/// +/// # Arguments +/// +/// * `self` - A span of bytes to serialize. +/// +/// # Returns +/// +/// * `Array` - The serialized bytes as an array of felt252 values. +pub fn serialize_bytes(self: Span) -> Array { + let mut array: Array = Default::default(); + + for item in self { + let value: felt252 = (*item).into(); + array.append(value); + }; + + array +} + +#[cfg(test)] +mod tests { + use core::starknet::secp256_trait::Signature; + use crate::constants::CHAIN_ID; + use crate::eth_transaction::tx_type::TxType; + use crate::serialization::{deserialize_signature, serialize_transaction_signature}; + + #[test] + fn test_serialize_transaction_signature() { + // generated via ./scripts/compute_rlp_encoding.ts + // inputs: + // to: 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 + // value: 1 + // gasLimit: 1 + // gasPrice: 1 + // nonce: 1 + // chainId: 1263227476 + // data: 0xabcdef + // tx_type: 0 for signature_0, 1 for signature_1, 2 for signature_2 + + // tx_type = 0, v: 0x9696a4cb + let signature_0 = Signature { + r: 0x306c3f638450a95f1f669481bf8ede9b056ef8d94259a3104f3a28673e02823d, + s: 0x41ea07e6d3d02773e380e752e5b3f9d28aca3882ee165e56b402cca0189967c9, + y_parity: false + }; + + // tx_type = 1 + let signature_1 = Signature { + r: 0x615c33039b7b09e3d5aa3cf1851c35abe7032f92111cc95ef45f83d032ccff5d, + s: 0x30b5f1a58abce1c7d45309b7a3b0befeddd1aee203021172779dd693a1e59505, + y_parity: false + }; + + // tx_type = 2 + let signature_2 = Signature { + r: 0xbc485ed0b43483ebe5fbff90962791c015755cc03060a33360b1b3e823bb71a4, + s: 0x4c47017509e1609db6c2e8e2b02327caeb709c986d8b63099695105432afa533, + y_parity: false + }; + + let expected_signature_0: Span = [ + signature_0.r.low.into(), + signature_0.r.high.into(), + signature_0.s.low.into(), + signature_0.s.high.into(), + 0x9696a4cb + ].span(); + + let expected_signature_1: Span = [ + signature_1.r.low.into(), + signature_1.r.high.into(), + signature_1.s.low.into(), + signature_1.s.high.into(), + 0x0_felt252, + ].span(); + + let expected_signature_2: Span = [ + signature_2.r.low.into(), + signature_2.r.high.into(), + signature_2.s.low.into(), + signature_2.s.high.into(), + 0x0_felt252, + ].span(); + + let result = serialize_transaction_signature(signature_0, TxType::Legacy, CHAIN_ID).span(); + assert_eq!(result, expected_signature_0); + + let result = serialize_transaction_signature(signature_1, TxType::Eip2930, CHAIN_ID).span(); + assert_eq!(result, expected_signature_1); + + let result = serialize_transaction_signature(signature_2, TxType::Eip1559, CHAIN_ID).span(); + assert_eq!(result, expected_signature_2); + } + + #[test] + fn test_deserialize_transaction_signature() { + // generated via ./scripts/compute_rlp_encoding.ts + // using json inputs + // to: 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 + // value: 1 + // gasLimit: 1 + // gasPrice: 1 + // nonce: 1 + // chainId: 1263227476 + // data: 0xabcdef + // tx_type: 0 for signature_0, 1 for signature_1, 2 for signature_2 + + // tx_type = 0, v: 0x9696a4cb + let signature_0 = Signature { + r: 0x5e5202c7e9d6d0964a1f48eaecf12eef1c3cafb2379dfeca7cbd413cedd4f2c7, + s: 0x66da52d0b666fc2a35895e0c91bc47385fe3aa347c7c2a129ae2b7b06cb5498b, + y_parity: false + }; + + // tx_type = 1 + let signature_1 = Signature { + r: 0xbced8d81c36fe13c95b883b67898b47b4b70cae79e89fa27856ddf8c533886d1, + s: 0x3de0109f00bc3ed95ffec98edd55b6f750cb77be8e755935dbd6cfec59da7ad0, + y_parity: true + }; + + // tx_type = 2 + let signature_2 = Signature { + r: 0x0f9a716653c19fefc240d1da2c5759c50f844fc8835c82834ea3ab7755f789a0, + s: 0x71506d904c05c6e5ce729b5dd88bcf29db9461c8d72413b864923e8d8f6650c0, + y_parity: true + }; + + // tx_type = 2 with false y parity - cf input_eip1559_y_parity_false.json + let signature_3 = Signature { + r: 0x782ce82b688abbf13a5e7536b27be67d2795a28b9d4bf819120c17630d88e609, + s: 0x43b90a3315977fe71c9b3687a89857544158e23c5045e7a5852bae03323c9898, + y_parity: false + }; + + let signature_0_felt252_arr: Array = array![ + signature_0.r.low.into(), + signature_0.r.high.into(), + signature_0.s.low.into(), + signature_0.s.high.into(), + 0x9696a4cb + ]; + + let signature_1_felt252_arr: Array = array![ + signature_1.r.low.into(), + signature_1.r.high.into(), + signature_1.s.low.into(), + signature_1.s.high.into(), + 0x1 + ]; + + let signature_2_felt252_arr: Array = array![ + signature_2.r.low.into(), + signature_2.r.high.into(), + signature_2.s.low.into(), + signature_2.s.high.into(), + 0x1 + ]; + + let signature_3_felt252_arr: Array = array![ + signature_3.r.low.into(), + signature_3.r.high.into(), + signature_3.s.low.into(), + signature_3.s.high.into(), + 0x0 + ]; + + let result: Signature = deserialize_signature(signature_0_felt252_arr.span(), CHAIN_ID) + .unwrap(); + assert_eq!(result, signature_0); + + let result: Signature = deserialize_signature(signature_1_felt252_arr.span(), CHAIN_ID) + .unwrap(); + assert_eq!(result, signature_1); + + let result: Signature = deserialize_signature(signature_2_felt252_arr.span(), CHAIN_ID) + .unwrap(); + assert_eq!(result, signature_2); + + let result: Signature = deserialize_signature(signature_3_felt252_arr.span(), CHAIN_ID) + .unwrap(); + assert_eq!(result, signature_3); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/set.cairo b/cairo/kakarot-ssj/crates/utils/src/set.cairo new file mode 100644 index 000000000..1b654bd53 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/set.cairo @@ -0,0 +1,281 @@ +use crate::traits::array::{SpanExtTrait, ArrayExtTrait}; + +/// A set implementation using an array as the underlying storage. +/// We are not using dicts because of the Destruct requirements. +#[derive(Drop, PartialEq)] +pub struct Set { + inner: Array +} + +pub impl SetDefault> of Default> { + #[inline] + fn default() -> Set { + let arr: Array = Default::default(); + Set { inner: arr } + } +} + +#[generate_trait] +pub impl SetImpl, +Copy> of SetTrait { + /// Creates a new, empty Set. + /// + /// # Returns + /// + /// A new, empty Set. + #[inline] + fn new() -> Set { + Set { inner: Default::default() } + } + + /// Creates a Set from an existing Array. + /// + /// # Arguments + /// + /// * `arr` - The Array to create the Set from. + /// + /// # Returns + /// + /// A new Set containing the elements from the input Array. + fn from_array(arr: Array) -> Set { + Set { inner: arr } + } + + /// Adds an item to the Set if it's not already present. + /// + /// # Arguments + /// + /// * `item` - The item to add to the Set. + #[inline] + fn add<+PartialEq>(ref self: Set, item: T) { + self.inner.append_unique(item); + } + + /// Extends the Set with elements from another SpanSet. + /// + /// # Arguments + /// + /// * `other` - The SpanSet to extend from. + #[inline] + fn extend<+PartialEq>(ref self: Set, other: SpanSet) { + self.extend_from_span(other.to_span()); + } + + /// Extends the Set with elements from a Span. + /// + /// # Arguments + /// + /// * `other` - The Span to extend from. + #[inline] + fn extend_from_span<+PartialEq>(ref self: Set, mut other: Span) { + for v in other { + self.add(*v); + }; + } + + /// Checks if the Set contains a specific item. + /// + /// # Arguments + /// + /// * `item` - The item to check for. + /// + /// # Returns + /// + /// `true` if the item is in the Set, `false` otherwise. + #[inline] + fn contains<+PartialEq>(self: @Set, item: T) -> bool { + self.inner.span().contains(item) + } + + /// Converts the Set to an Array. + /// + /// # Returns + /// + /// An Array containing all elements of the Set. + #[inline] + fn to_array(self: Set) -> Array { + self.inner + } + + /// Converts the Set to a Span. + /// + /// # Returns + /// + /// A Span view of the Set's elements. + #[inline] + fn to_span(self: @Set) -> Span { + self.inner.span() + } + + /// Creates a SpanSet from this Set. + /// + /// # Returns + /// + /// A SpanSet view of this Set. + #[inline] + fn spanset(self: @Set) -> SpanSet { + SpanSet { inner: self } + } + + /// Returns the number of elements in the Set. + /// + /// # Returns + /// + /// The number of elements in the Set. + #[inline] + fn len(self: @Set) -> usize { + self.inner.span().len() + } +} + +pub impl SetTCloneImpl, +Drop, +PartialEq, +Copy> of Clone> { + fn clone(self: @Set) -> Set { + let mut response: Array = Default::default(); + let mut span = self.to_span(); + for v in span { + response.append(*v); + }; + Set { inner: response } + } +} + +/// A read-only view of a Set. +#[derive(Copy, Drop, PartialEq)] +pub struct SpanSet { + inner: @Set +} + +/// Default implementation for SpanSet. +pub impl SpanSetDefault> of Default> { + /// Creates a new, empty SpanSet. + /// + /// # Returns + /// + /// A new, empty SpanSet. + #[inline] + fn default() -> SpanSet { + let set: Set = Default::default(); + SpanSet { inner: @set } + } +} + +#[generate_trait] +pub impl SpanSetImpl, +Drop> of SpanSetTrait { + /// Checks if the SpanSet contains a specific item. + /// + /// # Arguments + /// + /// * `item` - The item to check for. + /// + /// # Returns + /// + /// `true` if the item is in the SpanSet, `false` otherwise. + #[inline] + fn contains<+PartialEq>(self: SpanSet, item: T) -> bool { + self.inner.contains(item) + } + + /// Converts the SpanSet to a Span. + /// + /// # Returns + /// + /// A Span view of the SpanSet's elements. + #[inline] + fn to_span(self: SpanSet) -> Span { + self.inner.to_span() + } + + /// Creates a new Set from this SpanSet. + /// + /// # Returns + /// + /// A new Set with the same elements as this SpanSet. + fn clone_set(self: SpanSet) -> Set { + let mut response: Array = Default::default(); + let mut span = self.to_span(); + for v in span { + response.append(*v); + }; + Set { inner: response } + } + + /// Returns the number of elements in the SpanSet. + /// + /// # Returns + /// + /// The number of elements in the SpanSet. + #[inline] + fn len(self: SpanSet) -> usize { + self.inner.len() + } +} + +#[cfg(test)] +mod tests { + use crate::set::{Set, SetTrait, SpanSet, SpanSetTrait}; + + #[test] + fn test_add() { + let mut set: Set = Default::default(); + set.add(1); + set.add(2); + set.add(3); + set.add(3); + assert_eq!(set.len(), 3); + assert_eq!(set.contains(1), true); + assert_eq!(set.contains(2), true); + assert_eq!(set.contains(3), true); + assert_eq!(set.contains(4), false); + } + + #[test] + fn test_clone() { + let mut set: Set = Default::default(); + set.add(1); + set.add(2); + set.add(3); + set.add(3); + let mut set2 = set.clone(); + assert!(set == set2); + set2.add(4); + assert_eq!(set.len(), 3); + assert_eq!(set2.len(), 4); + assert_eq!(set.contains(1), true); + assert_eq!(set.contains(2), true); + assert_eq!(set.contains(3), true); + assert_eq!(set.contains(4), false); + assert_eq!(set2.contains(1), true); + assert_eq!(set2.contains(2), true); + assert_eq!(set2.contains(3), true); + assert_eq!(set2.contains(4), true); + } + + #[test] + fn test_spanset_clone_set() { + let mut set: Set = Default::default(); + set.add(1); + set.add(2); + let span_set = SpanSet { inner: @set }; + let set2 = span_set.clone_set(); + assert!(set == set2); + } + + #[test] + fn test_set_extend() { + let mut other: Set = Default::default(); + other.add(2); + other.add(1); + let other = other.spanset(); + + let mut set: Set = Default::default(); + set.add(3); + set.add(4); + set.extend(other); + + assert_eq!(set.len(), 4); + assert_eq!(set.contains(1), true); + assert_eq!(set.contains(2), true); + assert_eq!(set.contains(3), true); + assert_eq!(set.contains(4), true); + assert_eq!(set.contains(5), false); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/test_data.cairo b/cairo/kakarot-ssj/crates/utils/src/test_data.cairo new file mode 100644 index 000000000..b9084552b --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/test_data.cairo @@ -0,0 +1,421 @@ +pub fn legacy_rlp_encoded_tx() -> Span { + // tx_format (EIP-155, unsigned): [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0] + // expected rlp decoding: [ "0x", "0x3b9aca00", "0x1e8480", + // "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "0x016345785d8a0000", "0xabcdef", "0x4b4b5254", + // "0x", "0x" ] + // message_hash: 0xcf71743e6e25fef715398915997f782b95554c8bbfb7b3f7701e007332ed31b4 + // chain id used: 'KKRT' + [ + 243, + 128, + 132, + 59, + 154, + 202, + 0, + 131, + 30, + 132, + 128, + 148, + 31, + 152, + 64, + 168, + 93, + 90, + 245, + 191, + 29, + 23, + 98, + 249, + 37, + 189, + 173, + 220, + 66, + 1, + 249, + 132, + 136, + 1, + 99, + 69, + 120, + 93, + 138, + 0, + 0, + 131, + 171, + 205, + 239, + 132, + 75, + 75, + 82, + 84, + 128, + 128, + ].span() +} + +pub fn legacy_rlp_encoded_deploy_tx() -> Span { + // tx_format (EIP-155, unsigned): [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0] + // expected rlp decoding: + // ["0x","0x0a","0x061a80","0x","0x0186a0","0x600160010a5060006000f3","0x4b4b5254","0x","0x"] + [ + 222, + 128, + 10, + 131, + 6, + 26, + 128, + 128, + 131, + 1, + 134, + 160, + 139, + 96, + 1, + 96, + 1, + 10, + 80, + 96, + 0, + 96, + 0, + 243, + 132, + 75, + 75, + 82, + 84, + 128, + 128, + ].span() +} + + +pub fn eip_2930_encoded_tx() -> Span { + // tx_format (EIP-2930, unsigned): 0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, + // data, accessList]) + // expected rlp decoding: [ "0x4b4b5254", "0x", "0x3b9aca00", "0x1e8480", + // "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "0x016345785d8a0000", "0xabcdef", + // [["0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + // ["0xde9fbe35790b85c23f42b7430c78f122636750cc217a534c80a9a0520969fa65", + // "0xd5362e94136f76bfc8dad0b510b94561af7a387f1a9d0d45e777c11962e5bd94"]]] ] + // message_hash: 0xc00f61dcc99a78934275c404267b9d035cad7f71cf3ae2ed2c5a55b601a5c107 + // chain id used: 'KKRT' + [ + 1, + 248, + 142, + 132, + 75, + 75, + 82, + 84, + 128, + 132, + 59, + 154, + 202, + 0, + 131, + 30, + 132, + 128, + 148, + 31, + 152, + 64, + 168, + 93, + 90, + 245, + 191, + 29, + 23, + 98, + 249, + 37, + 189, + 173, + 220, + 66, + 1, + 249, + 132, + 136, + 1, + 99, + 69, + 120, + 93, + 138, + 0, + 0, + 131, + 171, + 205, + 239, + 248, + 91, + 248, + 89, + 148, + 31, + 152, + 64, + 168, + 93, + 90, + 245, + 191, + 29, + 23, + 98, + 249, + 37, + 189, + 173, + 220, + 66, + 1, + 249, + 132, + 248, + 66, + 160, + 222, + 159, + 190, + 53, + 121, + 11, + 133, + 194, + 63, + 66, + 183, + 67, + 12, + 120, + 241, + 34, + 99, + 103, + 80, + 204, + 33, + 122, + 83, + 76, + 128, + 169, + 160, + 82, + 9, + 105, + 250, + 101, + 160, + 213, + 54, + 46, + 148, + 19, + 111, + 118, + 191, + 200, + 218, + 208, + 181, + 16, + 185, + 69, + 97, + 175, + 122, + 56, + 127, + 26, + 157, + 13, + 69, + 231, + 119, + 193, + 25, + 98, + 229, + 189, + 148, + ].span() +} + +pub fn eip_1559_encoded_tx() -> Span { + // tx_format (EIP-1559, unsigned): 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, + // max_fee_per_gas, gas_limit, destination, amount, data, access_list]) + // expected rlp decoding: [ "0x4b4b5254", "0x", "0x", "0x3b9aca00", "0x1e8480", + // "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "0x016345785d8a0000", "0xabcdef", + // [[["0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + // ["0xde9fbe35790b85c23f42b7430c78f122636750cc217a534c80a9a0520969fa65", + // "0xd5362e94136f76bfc8dad0b510b94561af7a387f1a9d0d45e777c11962e5bd94"]]] ] ] + // message_hash: 0xa2de478d0c94b4be637523b818d03b6a1841fca63fd044976fcdbef3c57a87b0 + // chain id used: 'KKRT' + [ + 2, + 248, + 143, + 132, + 75, + 75, + 82, + 84, + 128, + 128, + 132, + 59, + 154, + 202, + 0, + 131, + 30, + 132, + 128, + 148, + 31, + 152, + 64, + 168, + 93, + 90, + 245, + 191, + 29, + 23, + 98, + 249, + 37, + 189, + 173, + 220, + 66, + 1, + 249, + 132, + 136, + 1, + 99, + 69, + 120, + 93, + 138, + 0, + 0, + 131, + 171, + 205, + 239, + 248, + 91, + 248, + 89, + 148, + 31, + 152, + 64, + 168, + 93, + 90, + 245, + 191, + 29, + 23, + 98, + 249, + 37, + 189, + 173, + 220, + 66, + 1, + 249, + 132, + 248, + 66, + 160, + 222, + 159, + 190, + 53, + 121, + 11, + 133, + 194, + 63, + 66, + 183, + 67, + 12, + 120, + 241, + 34, + 99, + 103, + 80, + 204, + 33, + 122, + 83, + 76, + 128, + 169, + 160, + 82, + 9, + 105, + 250, + 101, + 160, + 213, + 54, + 46, + 148, + 19, + 111, + 118, + 191, + 200, + 218, + 208, + 181, + 16, + 185, + 69, + 97, + 175, + 122, + 56, + 127, + 26, + 157, + 13, + 69, + 231, + 119, + 193, + 25, + 98, + 229, + 189, + 148, + ].span() +} diff --git a/cairo/kakarot-ssj/crates/utils/src/traits.cairo b/cairo/kakarot-ssj/crates/utils/src/traits.cairo new file mode 100644 index 000000000..e5bfc3eb8 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/traits.cairo @@ -0,0 +1,162 @@ +pub mod array; +pub mod bytes; +pub mod eth_address; +pub mod integer; + +use core::array::SpanTrait; +use core::num::traits::{Zero, One}; +use core::starknet::secp256_trait::{Signature}; +use core::starknet::storage_access::{StorageBaseAddress, storage_address_from_base}; +use core::starknet::{EthAddress, ContractAddress}; +use crate::math::{Bitshift}; +use evm::errors::{EVMError, ensure, TYPE_CONVERSION_ERROR}; + +pub impl DefaultSignature of Default { + #[inline(always)] + fn default() -> Signature { + Signature { r: 0, s: 0, y_parity: false, } + } +} + +pub impl SpanDefault> of Default> { + #[inline(always)] + fn default() -> Span { + array![].span() + } +} + +pub impl EthAddressDefault of Default { + #[inline(always)] + fn default() -> EthAddress { + 0.try_into().unwrap() + } +} + +pub impl ContractAddressDefault of Default { + #[inline(always)] + fn default() -> ContractAddress { + 0.try_into().unwrap() + } +} + +pub impl BoolIntoNumeric, +One> of Into { + #[inline(always)] + fn into(self: bool) -> T { + if self { + One::::one() + } else { + Zero::::zero() + } + } +} + +pub impl NumericIntoBool, +Zero, +One, +PartialEq> of Into { + #[inline(always)] + fn into(self: T) -> bool { + self != Zero::::zero() + } +} + +pub impl EthAddressIntoU256 of Into { + fn into(self: EthAddress) -> u256 { + let intermediate: felt252 = self.into(); + intermediate.into() + } +} + +pub impl U256TryIntoContractAddress of TryInto { + fn try_into(self: u256) -> Option { + let maybe_value: Option = self.try_into(); + match maybe_value { + Option::Some(value) => value.try_into(), + Option::None => Option::None, + } + } +} + +pub impl StorageBaseAddressIntoU256 of Into { + fn into(self: StorageBaseAddress) -> u256 { + let self: felt252 = storage_address_from_base(self).into(); + self.into() + } +} + +//TODO remove once merged in corelib +pub impl StorageBaseAddressPartialEq of PartialEq { + fn eq(lhs: @StorageBaseAddress, rhs: @StorageBaseAddress) -> bool { + let lhs: felt252 = (*lhs).into(); + let rhs: felt252 = (*rhs).into(); + lhs == rhs + } + fn ne(lhs: @StorageBaseAddress, rhs: @StorageBaseAddress) -> bool { + !(*lhs == *rhs) + } +} + +pub trait TryIntoResult { + fn try_into_result(self: T) -> Result; +} + +pub impl SpanU8TryIntoResultEthAddress of TryIntoResult, EthAddress> { + fn try_into_result(mut self: Span) -> Result { + let len = self.len(); + if len == 0 { + return Result::Ok(0.try_into().unwrap()); + } + ensure(!(len > 20), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR))?; + let offset: u32 = len.into() - 1; + let mut result: u256 = 0; + for i in 0 + ..len { + let byte: u256 = (*self.at(i)).into(); + result += byte.shl(8 * (offset - i).into()); + }; + let address: felt252 = result.try_into_result()?; + + Result::Ok(address.try_into().unwrap()) + } +} + +pub impl EthAddressTryIntoResultContractAddress of TryIntoResult { + fn try_into_result(self: ContractAddress) -> Result { + let tmp: felt252 = self.into(); + tmp.try_into().ok_or(EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)) + } +} + +pub impl U256TryIntoResult> of TryIntoResult { + fn try_into_result(self: u256) -> Result { + match self.try_into() { + Option::Some(value) => Result::Ok(value), + Option::None => Result::Err(EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)) + } + } +} + +pub impl U8IntoEthAddress of Into { + fn into(self: u8) -> EthAddress { + let value: felt252 = self.into(); + value.try_into().unwrap() + } +} + +#[cfg(test)] +mod tests { + use core::starknet::storage_access::storage_base_address_from_felt252; + use crate::traits::{StorageBaseAddressPartialEq}; + + #[test] + fn test_eq_storage_base_address() { + let val_1 = storage_base_address_from_felt252(0x01); + + assert_eq!(@val_1, @val_1) + } + + #[test] + fn test_ne_storage_base_address() { + let val_1 = storage_base_address_from_felt252(0x01); + let val_2 = storage_base_address_from_felt252(0x02); + + assert_ne!(@val_1, @val_2) + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/traits/array.cairo b/cairo/kakarot-ssj/crates/utils/src/traits/array.cairo new file mode 100644 index 000000000..35b3669f7 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/traits/array.cairo @@ -0,0 +1,143 @@ +#[generate_trait] +pub impl ArrayExtension> of ArrayExtTrait { + /// Concatenates two arrays by adding the elements of `arr2` to `self`. + /// + /// # Arguments + /// + /// * `self` - The array to append to. + /// * `arr2` - The array to append from. + fn concat<+Copy>(ref self: Array, mut arr2: Span) { + for elem in arr2 { + self.append(*elem); + }; + } + + /// Reverses an array. + /// + /// # Arguments + /// + /// * `self` - The array to reverse. + /// + /// # Returns + /// + /// A new `Array` with the elements in reverse order. + fn reverse<+Copy>(self: Span) -> Array { + let mut counter = self.len(); + let mut dst: Array = ArrayTrait::new(); + while counter != 0 { + dst.append(*self[counter - 1]); + counter -= 1; + }; + dst + } + + /// Appends a value to the array `n` times. + /// + /// # Arguments + /// + /// * `self` - The array to append to. + /// * `value` - The value to append. + /// * `n` - The number of times to append the value. + fn append_n<+Copy>(ref self: Array, value: T, mut n: usize) { + for _ in 0..n { + self.append(value); + }; + } + + /// Appends an item only if it is not already in the array. + /// + /// # Arguments + /// + /// * `self` - The array to append to. + /// * `value` - The value to append if not present. + fn append_unique<+Copy, +PartialEq>(ref self: Array, value: T) { + if self.span().contains(value) { + return (); + } + self.append(value); + } + + /// Concatenates two arrays by adding the unique elements of `arr2` to `self`. + /// + /// # Arguments + /// + /// * `self` - The array to append to. + /// * `arr2` - The array to append from. + fn concat_unique<+Copy, +PartialEq>(ref self: Array, mut arr2: Span) { + for elem in arr2 { + self.append_unique(*elem) + }; + } +} + +#[generate_trait] +pub impl SpanExtension, +Drop> of SpanExtTrait { + /// Returns true if the array contains an item. + /// + /// # Arguments + /// + /// * `self` - The array to search. + /// * `value` - The value to search for. + /// + /// # Returns + /// + /// `true` if the value is found, `false` otherwise. + fn contains<+PartialEq>(mut self: Span, value: T) -> bool { + let mut result = false; + for elem in self { + if *elem == value { + result = true; + } + }; + result + } + + /// Returns the index of an item in the array. + /// + /// # Arguments + /// + /// * `self` - The array to search. + /// * `value` - The value to search for. + /// + /// # Returns + /// + /// `Option::Some(index)` if the value is found, `Option::None` otherwise. + fn index_of<+PartialEq>(mut self: Span, value: T) -> Option { + let mut i = 0; + let mut result = Option::None; + for elem in self { + if *elem == value { + result = Option::Some(i); + } + i += 1; + }; + return result; + } +} + +#[cfg(test)] +mod tests { + mod test_array_ext { + use super::super::{ArrayExtTrait}; + #[test] + fn test_append_n() { + // Given + let mut original: Array = array![1, 2, 3, 4]; + + // When + original.append_n(9, 3); + + // Then + assert(original == array![1, 2, 3, 4, 9, 9, 9], 'append_n failed'); + } + + #[test] + fn test_append_unique() { + let mut arr = array![1, 2, 3]; + arr.append_unique(4); + assert(arr == array![1, 2, 3, 4], 'should have appended'); + arr.append_unique(2); + assert(arr == array![1, 2, 3, 4], 'shouldnt have appended'); + } + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/traits/bytes.cairo b/cairo/kakarot-ssj/crates/utils/src/traits/bytes.cairo new file mode 100644 index 000000000..177b60a79 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/traits/bytes.cairo @@ -0,0 +1,769 @@ +use core::cmp::min; +use core::keccak::{cairo_keccak}; +use core::num::traits::{Zero, One, Bounded, BitSize, SaturatingAdd}; +use core::traits::{BitAnd}; +use crate::constants::{POW_2, POW_256_1, POW_256_REV}; +use crate::math::{Bitshift}; +use crate::traits::integer::{BytesUsedTrait, ByteSize, U256Trait}; + +#[generate_trait] +pub impl U8SpanExImpl of U8SpanExTrait { + /// Computes the keccak256 hash of a bytes message + /// # Arguments + /// * `self` - The input bytes as a Span + /// # Returns + /// * The keccak256 hash as a u256 + fn compute_keccak256_hash(self: Span) -> u256 { + let (mut keccak_input, last_input_word, last_input_num_bytes) = self.to_u64_words(); + let hash = cairo_keccak(ref keccak_input, :last_input_word, :last_input_num_bytes) + .reverse_endianness(); + + hash + } + + /// Transforms a Span into an Array of u64 full words, a pending u64 word and its length in + /// bytes + /// # Arguments + /// * `self` - The input bytes as a Span + /// # Returns + /// * A tuple containing: + /// - An Array of full words + /// - A u64 representing the last (potentially partial) word + /// - A usize representing the number of bytes in the last word + fn to_u64_words(self: Span) -> (Array, u64, usize) { + let nonzero_8: NonZero = 8_u32.try_into().unwrap(); + let (_, last_input_num_bytes) = DivRem::div_rem(self.len(), nonzero_8); + + let mut u64_words: Array = array![]; + let mut word_index = 0; + let mut byte_counter: u8 = 0; + let mut pending_word: u64 = 0; + + // Iterate until we have iterated over all the full words and the last word + while true { + if byte_counter == 8 { + u64_words.append(pending_word); + word_index += 8; + byte_counter = 0; + pending_word = 0; + } + pending_word += match self.get(word_index + byte_counter.into()) { + Option::Some(byte) => { + let byte: u64 = (*byte.unbox()).into(); + // Accumulate pending_word in a little endian manner + byte.shl(8_u32 * byte_counter.into()) + }, + Option::None => { break; }, + }; + byte_counter += 1; + }; + let last_input_word: u64 = pending_word; + (u64_words, last_input_word, last_input_num_bytes) + } + + /// Returns right padded slice of the span, starting from index offset + /// If offset is greater than the span length, returns an empty span + /// # Examples + /// + /// ``` + /// let span = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05].span(); + /// let expected = [0x04, 0x05, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].span(); + /// let result = span.slice_right_padded(4, 10); + /// assert_eq!(result, expected); + /// ``` + /// # Arguments + /// * `offset` - The offset to start the slice from + /// * `len` - The length of the slice + /// + /// # Returns + /// * A span of length `len` starting from `offset` right padded with 0s if `offset` is greater + /// than the span length, returns an empty span of length `len` if offset is grearter than the + /// span length + fn slice_right_padded(self: Span, offset: usize, len: usize) -> Span { + let start = if offset <= self.len() { + offset + } else { + self.len() + }; + + let end = min(start.saturating_add(len), self.len()); + + let slice = self.slice(start, end - start); + // Save appending to span for this case as it is more efficient to just return the slice + if slice.len() == len { + return slice; + } + + // Copy the span + let mut arr = array![]; + arr.append_span(slice); + + while arr.len() != len { + arr.append(0); + }; + + arr.span() + } + + /// Clones and pads the given span with 0s to the right to the given length + /// If data is more than the given length, it is truncated from the right side + /// # Arguments + /// * `self` - The input bytes as a Span + /// * `len` - The desired length of the padded span + /// # Returns + /// * A Span of length `len` right padded with 0s if the span length is less than `len`, + /// or truncated from the right if the span length is greater than `len` + /// # Examples + /// ``` + /// let span = array![1, 2, 3].span(); + /// let padded = span.pad_right_with_zeroes(5); + /// assert_eq!(padded, array![1, 2, 3, 0, 0].span()); + /// ``` + fn pad_right_with_zeroes(self: Span, len: usize) -> Span { + if self.len() >= len { + return self.slice(0, len); + } + + // Create a new array with the original data + let mut arr = array![]; + for i in self { + arr.append(*i); + }; + + // Pad with zeroes + while arr.len() != len { + arr.append(0); + }; + + arr.span() + } + + + /// Clones and pads the given span with 0s to the left to the given length + /// If data is more than the given length, it is truncated from the right side + /// # Arguments + /// * `self` - The input bytes as a Span + /// * `len` - The desired length of the padded span + /// # Returns + /// * A Span of length `len` left padded with 0s if the span length is less than `len`, + /// or truncated from the right if the span length is greater than `len` + /// # Examples + /// ``` + /// let span = array![1, 2, 3].span(); + /// let padded = span.pad_left_with_zeroes(5); + /// assert_eq!(padded, array![0, 0, 1, 2, 3].span()); + /// ``` + fn pad_left_with_zeroes(self: Span, len: usize) -> Span { + if self.len() >= len { + return self.slice(0, len); + } + + // left pad with 0 + let mut arr = array![]; + while arr.len() != (len - self.len()) { + arr.append(0); + }; + + // append the data + for item in self { + arr.append(*item); + }; + + arr.span() + } +} + +pub trait ToBytes { + /// Unpacks a type T into a span of big endian bytes + /// + /// # Arguments + /// * `self` a value of type T. + /// + /// # Returns + /// * The bytes representation of the value in big endian. + fn to_be_bytes(self: T) -> Span; + /// Unpacks a type T into a span of big endian bytes, padded to the byte size of T + /// + /// # Arguments + /// * `self` a value of type T. + /// + /// # Returns + /// * The bytesrepresentation of the value in big endian padded to the byte size of T. + fn to_be_bytes_padded(self: T) -> Span; + /// Unpacks a type T into a span of little endian bytes + /// + /// # Arguments + /// * `self` a value of type T. + /// + /// # Returns + /// * The bytes representation of the value in little endian. + fn to_le_bytes(self: T) -> Span; + /// Unpacks a type T into a span of little endian bytes, padded to the byte size of T + /// + /// # Arguments + /// * `self` a value of type T. + /// + /// # Returns + /// * The bytesrepresentation of the value in little endian padded to the byte size of T. + fn to_le_bytes_padded(self: T) -> Span; +} + +pub impl U8ToBytes of ToBytes { + fn to_be_bytes(self: u8) -> Span { + [self].span() + } + + fn to_be_bytes_padded(self: u8) -> Span { + self.to_be_bytes() + } + + fn to_le_bytes(self: u8) -> Span { + [self].span() + } + + fn to_le_bytes_padded(self: u8) -> Span { + self.to_le_bytes() + } +} + +pub impl ToBytesImpl< + T, + +Zero, + +One, + +Add, + +Sub, + +Mul, + +Div, + +BitAnd, + +Bitshift, + +BitSize, + +BytesUsedTrait, + +Into, + +TryInto, + +Copy, + +Drop, + +core::ops::AddAssign, + +PartialEq +> of ToBytes { + fn to_be_bytes(self: T) -> Span { + // U8 type is handled in another impl. + let be_bytes = to_be_bytes_recursive(self); + be_bytes.span() + } + + fn to_be_bytes_padded(mut self: T) -> Span { + let padding = (BitSize::::bits() / 8); + self.to_be_bytes().pad_left_with_zeroes(padding) + } + + fn to_le_bytes(mut self: T) -> Span { + // U8 type is handled in another impl. + + let bytes_used = self.bytes_used(); + + // 0xFF + let mask: u16 = Bounded::::MAX.into(); + let mask: T = mask.into(); + + let mut bytes: Array = Default::default(); + let mut value = self; + + for _ in 0 + ..bytes_used { + bytes.append((value & mask).try_into().unwrap()); + value = value / 256_u16.into(); + }; + + bytes.span() + } + + fn to_le_bytes_padded(mut self: T) -> Span { + let padding = (BitSize::::bits() / 8); + self.to_le_bytes().slice_right_padded(0, padding) + } +} + +// Helper function to recursively build the bytes +fn to_be_bytes_recursive< + T, + +Zero, + +One, + +Add, + +Sub, + +Mul, + +Div, + +BitAnd, + +Bitshift, + +BitSize, + +BytesUsedTrait, + +Into, + +TryInto, + +Copy, + +Drop, + +core::ops::AddAssign, + +PartialEq +>( + value: T +) -> Array { + // Base case: if value is 0, unpile the call stack + if value == 0_u16.into() { + return array![]; + } + + // 0xFF + let mask: u16 = Bounded::::MAX.into(); + let mask: T = mask.into(); + + // Get the least significant byte + let byte: u8 = (value & mask).try_into().unwrap(); + + // Recurse with the value shifted right by 8 bits + let mut be_bytes = to_be_bytes_recursive(value / (256_u16.into())); + be_bytes.append(byte); + return be_bytes; +} + +pub trait FromBytes { + /// Parses a span of big endian bytes into a type T + /// + /// # Arguments + /// * `self` a span of big endian bytes. + /// + /// # Returns + /// * The Option::(value) represented by the bytes in big endian, Option::None if the span is + /// not the byte size of T. + fn from_be_bytes(self: Span) -> Option; + + /// Parses a span of big endian bytes into a type T, allowing for partial input + /// + /// # Arguments + /// * `self` a span of big endian bytes. + /// + /// # Returns + /// * The Option::(value) represented by the bytes in big endian, Option::None if the span is + /// longer than the byte size of T. + fn from_be_bytes_partial(self: Span) -> Option; + + + /// Parses a span of little endian bytes into a type T + /// + /// # Arguments + /// * `self` a span of little endian bytes. + /// + /// # Returns + /// * The Option::(value) represented by the bytes in little endian, Option::None if the span is + /// not the byte size of T. + fn from_le_bytes(self: Span) -> Option; + + /// Parses a span of little endian bytes into a type T, allowing for partial input + /// + /// # Arguments + /// * `self` a span of little endian bytes. + /// + /// # Returns + /// * The Option::(value) represented by the bytes in little endian, Option::None if the span is + /// longer than the byte size of T. + fn from_le_bytes_partial(self: Span) -> Option; +} + +pub impl FromBytesImpl< + T, + +Zero, + +One, + +Add, + +Sub, + +Mul, + +BitAnd, + +Bitshift, + +ByteSize, + +BytesUsedTrait, + +Into, + +Into, + +TryInto, + +Copy, + +Drop, + +core::ops::AddAssign, + +PartialEq +> of FromBytes { + fn from_be_bytes(self: Span) -> Option { + let byte_size = ByteSize::::byte_size(); + + if self.len() != byte_size { + return Option::None; + } + + let mut result: T = Zero::zero(); + for byte in self { + let tmp = result * 256_u16.into(); + result = tmp + (*byte).into(); + }; + Option::Some(result) + } + + fn from_be_bytes_partial(self: Span) -> Option { + let byte_size = ByteSize::::byte_size(); + + if self.len() > byte_size { + return Option::None; + } + + let mut result: T = Zero::zero(); + for byte in self { + let tmp = result * 256_u16.into(); + result = tmp + (*byte).into(); + }; + + Option::Some(result) + } + + fn from_le_bytes(mut self: Span) -> Option { + let byte_size = ByteSize::::byte_size(); + + if self.len() != byte_size { + return Option::None; + } + + let mut result: T = Zero::zero(); + loop { + match self.pop_back() { + Option::None => { break; }, + Option::Some(byte) => { + let tmp = result * 256_u16.into(); + result = tmp + (*byte).into(); + } + }; + }; + Option::Some(result) + } + + fn from_le_bytes_partial(mut self: Span) -> Option { + let byte_size = ByteSize::::byte_size(); + + if self.len() > byte_size { + return Option::None; + } + + let mut result: T = Zero::zero(); + loop { + match self.pop_back() { + Option::None => { break; }, + Option::Some(byte) => { + let tmp = result * 256_u16.into(); + result = tmp + (*byte).into(); + } + }; + }; + Option::Some(result) + } +} + + +#[generate_trait] +pub impl ByteArrayExt of ByteArrayExTrait { + /// Appends a span of bytes to the ByteArray + /// # Arguments + /// * `self` - The ByteArray to append to + /// * `bytes` - The span of bytes to append + fn append_span_bytes(ref self: ByteArray, mut bytes: Span) { + for val in bytes { + self.append_byte(*val); + }; + } + + /// Creates a ByteArray from a span of bytes + /// # Arguments + /// * `bytes` - The span of bytes to convert + /// # Returns + /// * A new ByteArray containing the input bytes + fn from_bytes(mut bytes: Span) -> ByteArray { + let mut arr: ByteArray = Default::default(); + let (nb_full_words, pending_word_len) = DivRem::div_rem( + bytes.len(), 31_u32.try_into().unwrap() + ); + for _ in 0 + ..nb_full_words { + let mut word: felt252 = 0; + for _ in 0 + ..31_u8 { + word = word * POW_256_1.into() + (*bytes.pop_front().unwrap()).into(); + }; + arr.append_word(word.try_into().unwrap(), 31); + }; + + if pending_word_len == 0 { + return arr; + }; + + let mut pending_word: felt252 = 0; + + for _ in 0 + ..pending_word_len { + pending_word = pending_word * POW_256_1.into() + + (*bytes.pop_front().unwrap()).into(); + }; + arr.append_word(pending_word.try_into().unwrap(), pending_word_len); + arr + } + + /// Checks if the ByteArray is empty + /// # Arguments + /// * `self` - The ByteArray to check + /// # Returns + /// * true if the ByteArray is empty, false otherwise + fn is_empty(self: @ByteArray) -> bool { + self.len() == 0 + } + + /// Converts the ByteArray into a span of bytes + /// # Arguments + /// * `self` - The ByteArray to convert + /// # Returns + /// * A Span containing the bytes from the ByteArray + fn into_bytes(self: ByteArray) -> Span { + let mut output: Array = Default::default(); + for i in 0..self.len() { + output.append(self[i]); + }; + output.span() + } +} + + +#[cfg(test)] +mod tests { + mod bytearray_test { + use super::super::{ByteArrayExTrait}; + #[test] + fn test_pack_bytes_ge_bytes31() { + let mut arr = array![ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1A, + 0x1B, + 0x1C, + 0x1D, + 0x1E, + 0x1F, + 0x20, + 0x21 // 33 elements + ]; + + let res = ByteArrayExTrait::from_bytes(arr.span()); + + // Ensure that the result is complete and keeps the same order + for i in 0..arr.len() { + assert(*arr[i] == res[i], 'byte mismatch'); + }; + } + + #[test] + fn test_bytearray_append_span_bytes() { + let bytes = array![0x01, 0x02, 0x03, 0x04]; + let mut byte_arr: ByteArray = Default::default(); + byte_arr.append_byte(0xFF); + byte_arr.append_byte(0xAA); + byte_arr.append_span_bytes(bytes.span()); + assert_eq!(byte_arr.len(), 6); + assert_eq!(byte_arr[0], 0xFF); + assert_eq!(byte_arr[1], 0xAA); + assert_eq!(byte_arr[2], 0x01); + assert_eq!(byte_arr[3], 0x02); + assert_eq!(byte_arr[4], 0x03); + assert_eq!(byte_arr[5], 0x04); + } + + #[test] + fn test_byte_array_into_bytes() { + let input = array![ + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1A, + 0x1B, + 0x1C, + 0x1D, + 0x1E, + 0x1F, + 0x20, + 0x21 // 33 elements + ]; + let byte_array = ByteArrayExTrait::from_bytes(input.span()); + let res = byte_array.into_bytes(); + + // Ensure that the elements are correct + assert(res == input.span(), 'bytes mismatch'); + } + + #[test] + fn test_pack_bytes_le_bytes31() { + let mut arr = array![0x11, 0x22, 0x33, 0x44]; + let res = ByteArrayExTrait::from_bytes(arr.span()); + + // Ensure that the result is complete and keeps the same order + for i in 0..arr.len() { + assert(*arr[i] == res[i], 'byte mismatch'); + }; + } + } + + + mod span_u8_test { + use super::super::{U8SpanExTrait, ToBytes}; + + #[test] + fn test_span_u8_to_64_words_partial() { + let mut input: Span = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06].span(); + let (u64_words, pending_word, pending_word_len) = input.to_u64_words(); + assert(pending_word == 6618611909121, 'wrong pending word'); + assert(pending_word_len == 6, 'wrong pending word length'); + assert(u64_words.len() == 0, 'wrong u64 words length'); + } + + #[test] + fn test_span_u8_to_64_words_full() { + let mut input: Span = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08].span(); + let (u64_words, pending_word, pending_word_len) = input.to_u64_words(); + + assert(pending_word == 0, 'wrong pending word'); + assert(pending_word_len == 0, 'wrong pending word length'); + assert(u64_words.len() == 1, 'wrong u64 words length'); + assert(*u64_words[0] == 578437695752307201, 'wrong u64 words length'); + } + + + #[test] + fn test_compute_msg_hash() { + let msg = 0xabcdef_u32.to_be_bytes(); + let expected_hash = 0x800d501693feda2226878e1ec7869eef8919dbc5bd10c2bcd031b94d73492860; + let hash = msg.compute_keccak256_hash(); + + assert_eq!(hash, expected_hash); + } + + #[test] + fn test_right_padded_span_offset_0() { + let span = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05].span(); + let expected = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x0, 0x0, 0x0, 0x0].span(); + let result = span.slice_right_padded(0, 10); + + assert_eq!(result, expected); + } + + #[test] + fn test_right_padded_span_offset_4() { + let span = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05].span(); + let expected = [0x04, 0x05, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].span(); + let result = span.slice_right_padded(4, 10); + + assert_eq!(result, expected); + } + + #[test] + fn test_right_padded_span_offset_greater_than_span_len() { + let span = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05].span(); + let expected = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].span(); + let result = span.slice_right_padded(6, 10); + + assert_eq!(result, expected); + } + + #[test] + fn test_pad_left_with_zeroes_len_10() { + let span = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05].span(); + let expected = [0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0x02, 0x03, 0x04, 0x05].span(); + let result = span.pad_left_with_zeroes(10); + + assert_eq!(result, expected); + } + + #[test] + fn test_pad_left_with_zeroes_len_equal_than_data_len() { + let span = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x8, 0x9].span(); + let expected = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x8, 0x9].span(); + let result = span.pad_left_with_zeroes(10); + + assert_eq!(result, expected); + } + + #[test] + fn test_pad_left_with_zeroes_len_equal_than_smaller_len() { + let span = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x8, 0x9].span(); + let expected = [0x0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x8].span(); + let result = span.pad_left_with_zeroes(9); + + assert_eq!(result, expected); + } + + #[test] + fn test_pad_right_with_zeroes_len_10() { + let span = [0x01, 0x02, 0x03, 0x04, 0x05].span(); + let expected = [0x01, 0x02, 0x03, 0x04, 0x05, 0x0, 0x0, 0x0, 0x0, 0x0].span(); + let result = span.pad_right_with_zeroes(10); + + assert_eq!(result, expected); + } + + #[test] + fn test_pad_right_with_zeroes_truncate() { + let span = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a].span(); + let expected = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09].span(); + let result = span.pad_right_with_zeroes(9); + + assert_eq!(result, expected); + } + + #[test] + fn test_pad_right_with_zeroes_same_length() { + let span = [0x01, 0x02, 0x03, 0x04, 0x05].span(); + let expected = [0x01, 0x02, 0x03, 0x04, 0x05].span(); + let result = span.pad_right_with_zeroes(5); + + assert_eq!(result, expected); + } + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/traits/eth_address.cairo b/cairo/kakarot-ssj/crates/utils/src/traits/eth_address.cairo new file mode 100644 index 000000000..d039f1de4 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/traits/eth_address.cairo @@ -0,0 +1,147 @@ +use core::starknet::EthAddress; +use crate::math::Bitshift; +use crate::traits::EthAddressIntoU256; + +#[generate_trait] +pub impl EthAddressExImpl of EthAddressExTrait { + const BYTES_USED: u8 = 20; + /// Converts an EthAddress to an array of bytes. + /// + /// # Returns + /// + /// * `Array` - A 20-byte array representation of the EthAddress. + fn to_bytes(self: EthAddress) -> Array { + let value: u256 = self.into(); + let mut bytes: Array = Default::default(); + for i in 0 + ..Self::BYTES_USED { + let val = value.shr(8_u32 * (Self::BYTES_USED.into() - i.into() - 1)); + bytes.append((val & 0xFF).try_into().unwrap()); + }; + + bytes + } + + /// Converts a 20-byte array into an EthAddress. + /// + /// # Arguments + /// + /// * `input` - A `Span` of length 20 representing the bytes of an Ethereum address. + /// + /// # Returns + /// + /// * `Option` - `Some(EthAddress)` if the conversion succeeds, `None` if the input + /// length is not 20. + fn from_bytes(input: Span) -> Option { + let len = input.len(); + if len != 20 { + return Option::None; + } + let offset: u32 = len - 1; + let mut result: u256 = 0; + for i in 0 + ..len { + let byte: u256 = (*input.at(i)).into(); + result += byte.shl((8 * (offset - i))); + }; + result.try_into() + } +} + +#[cfg(test)] +mod tests { + use core::starknet::EthAddress; + use super::EthAddressExTrait; + #[test] + fn test_eth_address_to_bytes() { + let eth_address: EthAddress = 0x1234567890123456789012345678901234567890 + .try_into() + .unwrap(); + let bytes = eth_address.to_bytes(); + assert_eq!( + bytes.span(), + [ + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90 + ].span() + ); + } + + #[test] + fn test_eth_address_from_bytes() { + let bytes = [ + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90 + ].span(); + let eth_address = EthAddressExTrait::from_bytes(bytes); + assert_eq!( + eth_address, + Option::Some(0x1234567890123456789012345678901234567890.try_into().unwrap()) + ); + } + + #[test] + fn test_eth_address_from_bytes_invalid_length() { + let bytes = [ + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12, + 0x34, + 0x56, + 0x78, + 0x90, + 0x12 + ]; + let eth_address = EthAddressExTrait::from_bytes(bytes.span()); + assert_eq!(eth_address, Option::None); + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/traits/integer.cairo b/cairo/kakarot-ssj/crates/utils/src/traits/integer.cairo new file mode 100644 index 000000000..6fd837630 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/traits/integer.cairo @@ -0,0 +1,1000 @@ +use core::integer::{u128_byte_reverse}; +use core::num::traits::{Zero, One, Bounded, BitSize}; +use crate::helpers::{u128_split}; +use crate::math::{Bitshift}; + +#[generate_trait] +pub impl U64Impl of U64Trait { + /// Returns the number of trailing zeroes in the bit representation of `self`. + /// # Arguments + /// * `self` a `u64` value. + /// # Returns + /// * The number of trailing zeroes in the bit representation of `self`. + fn count_trailing_zeroes(self: u64) -> u8 { + let mut count = 0; + + if self == 0 { + return 64; // If n is 0, all 64 bits are zeros + }; + + let mut mask = 1; + + while (self & mask) == 0 { + count += 1; + mask *= 2; + }; + + count + } +} + + +#[generate_trait] +pub impl U128Impl of U128Trait { + /// Returns the Least significant 64 bits of a u128 + fn as_u64(self: u128) -> u64 { + let (_, bottom_word) = u128_split(self); + bottom_word + } +} + +#[generate_trait] +pub impl U256Impl of U256Trait { + /// Splits an u256 into 4 little endian u64. + /// Returns ((high_high, high_low),(low_high, low_low)) + fn split_into_u64_le(self: u256) -> ((u64, u64), (u64, u64)) { + let low_le = u128_byte_reverse(self.low); + let high_le = u128_byte_reverse(self.high); + (u128_split(high_le), u128_split(low_le)) + } + + /// Reverse the endianness of an u256 + fn reverse_endianness(self: u256) -> u256 { + let new_low = u128_byte_reverse(self.high); + let new_high = u128_byte_reverse(self.low); + u256 { low: new_low, high: new_high } + } +} + +pub trait BytesUsedTrait { + /// Returns the number of bytes used to represent a `T` value. + /// # Arguments + /// * `self` - The value to check. + /// # Returns + /// The number of bytes used to represent the value. + fn bytes_used(self: T) -> u8; +} + +pub impl U8BytesUsedTraitImpl of BytesUsedTrait { + fn bytes_used(self: u8) -> u8 { + if self == 0 { + return 0; + } + + return 1; + } +} + +pub impl USizeBytesUsedTraitImpl of BytesUsedTrait { + fn bytes_used(self: usize) -> u8 { + if self < 0x10000 { // 256^2 + if self < 0x100 { // 256^1 + if self == 0 { + return 0; + } else { + return 1; + }; + } + return 2; + } else { + if self < 0x1000000 { // 256^3 + return 3; + } + return 4; + } + } +} + +pub impl U64BytesUsedTraitImpl of BytesUsedTrait { + fn bytes_used(self: u64) -> u8 { + if self <= Bounded::::MAX.into() { // 256^4 + return BytesUsedTrait::::bytes_used(self.try_into().unwrap()); + } else { + if self < 0x1000000000000 { // 256^6 + if self < 0x10000000000 { + if self < 0x100000000 { + return 4; + } + return 5; + } + return 6; + } else { + if self < 0x100000000000000 { // 256^7 + return 7; + } else { + return 8; + } + } + } + } +} + +pub impl U128BytesTraitUsedImpl of BytesUsedTrait { + fn bytes_used(self: u128) -> u8 { + let (u64high, u64low) = u128_split(self); + if u64high == 0 { + return BytesUsedTrait::::bytes_used(u64low.try_into().unwrap()); + } else { + return BytesUsedTrait::::bytes_used(u64high.try_into().unwrap()) + 8; + } + } +} + +pub impl U256BytesUsedTraitImpl of BytesUsedTrait { + fn bytes_used(self: u256) -> u8 { + if self.high == 0 { + return BytesUsedTrait::::bytes_used(self.low.try_into().unwrap()); + } else { + return BytesUsedTrait::::bytes_used(self.high.try_into().unwrap()) + 16; + } + } +} + +pub trait ByteSize { + fn byte_size() -> usize; +} + +pub impl ByteSizeImpl> of ByteSize { + fn byte_size() -> usize { + BitSize::::bits() / 8 + } +} + +pub trait BitsUsed { + /// Returns the number of bits required to represent `self`, ignoring leading zeros. + /// # Arguments + /// `self` - The value to check. + /// # Returns + /// The number of bits used to represent the value, ignoring leading zeros. + fn bits_used(self: T) -> u32; + + /// Returns the number of leading zeroes in the bit representation of `self`. + /// # Arguments + /// `self` - The value to check. + /// # Returns + /// The number of leading zeroes in the bit representation of `self`. + fn count_leading_zeroes(self: T) -> u32; +} + +pub impl BitsUsedImpl< + T, + +Zero, + +One, + +Add, + +Sub, + +Mul, + +Bitshift, + +BitSize, + +BytesUsedTrait, + +Into, + +TryInto, + +Copy, + +Drop, + +PartialEq +> of BitsUsed { + fn bits_used(self: T) -> u32 { + if self == Zero::zero() { + return 0; + } + + let bytes_used = self.bytes_used(); + let last_byte = self.shr(8_u32 * (bytes_used.into() - One::one())); + + // safe unwrap since we know at most 8 bits are used + let bits_used: u8 = bits_used_internal::bits_used_in_byte(last_byte.try_into().unwrap()); + + bits_used.into() + 8 * (bytes_used - 1).into() + } + + fn count_leading_zeroes(self: T) -> u32 { + BitSize::::bits() - self.bits_used() + } +} + +pub(crate) mod bits_used_internal { + /// Returns the number of bits used to represent the value in binary representation + /// # Arguments + /// * `self` - The value to compute the number of bits used + /// # Returns + /// * The number of bits used to represent the value in binary representation + pub(crate) fn bits_used_in_byte(self: u8) -> u8 { + if self < 0b100000 { + if self < 0b1000 { + if self < 0b100 { + if self < 0b10 { + if self == 0 { + return 0; + } else { + return 1; + }; + } + return 2; + } + + return 3; + } + + if self < 0b10000 { + return 4; + } + + return 5; + } else { + if self < 0b10000000 { + if self < 0b1000000 { + return 6; + } + return 7; + } + return 8; + } + } +} + +#[cfg(test)] +mod tests { + mod u8_test { + use crate::math::Bitshift; + use crate::traits::bytes::{ToBytes, FromBytes}; + use super::super::BitsUsed; + + #[test] + fn test_bits_used() { + assert_eq!(0x00_u8.bits_used(), 0); + let mut value: u8 = 0xff; + let mut i = 8; + loop { + assert_eq!(value.bits_used(), i); + if i == 0 { + break; + }; + value = value.shr(1); + + i -= 1; + }; + } + + #[test] + fn test_u8_to_le_bytes() { + let input: u8 = 0xf4; + let res: Span = input.to_le_bytes(); + + assert_eq!(res, [0xf4].span()); + } + + #[test] + fn test_u8_to_le_bytes_padded() { + let input: u8 = 0xf4; + let res: Span = input.to_le_bytes_padded(); + + assert_eq!(res, [0xf4].span()); + } + } + + mod u32_test { + use crate::math::Bitshift; + use crate::traits::bytes::{ToBytes, FromBytes}; + use super::super::{BytesUsedTrait}; + + #[test] + fn test_u32_from_be_bytes() { + let input: Array = array![0xf4, 0x32, 0x15, 0x62]; + let res: Option = input.span().from_be_bytes(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xf4321562); + } + + #[test] + fn test_u32_from_be_bytes_too_big_should_return_none() { + let input: Array = array![0xf4, 0x32, 0x15, 0x62, 0x01]; + let res: Option = input.span().from_be_bytes(); + + assert!(res.is_none()); + } + + #[test] + fn test_u32_from_be_bytes_too_small_should_return_none() { + let input: Array = array![0xf4, 0x32, 0x15]; + let res: Option = input.span().from_be_bytes(); + + assert!(res.is_none()); + } + + #[test] + fn test_u32_from_be_bytes_partial() { + let input: Array = array![0xf4, 0x32, 0x15, 0x62]; + let res: Option = input.span().from_be_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xf4321562); + } + + #[test] + fn test_u32_from_be_bytes_partial_smaller_input() { + let input: Array = array![0xf4, 0x32, 0x15]; + let res: Option = input.span().from_be_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xf43215); + } + + #[test] + fn test_u32_from_be_bytes_partial_single_byte() { + let input: Array = array![0xf4]; + let res: Option = input.span().from_be_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xf4); + } + + #[test] + fn test_u32_from_be_bytes_partial_empty_input() { + let input: Array = array![]; + let res: Option = input.span().from_be_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0); + } + + #[test] + fn test_u32_from_be_bytes_partial_too_big_input() { + let input: Array = array![0xf4, 0x32, 0x15, 0x62, 0x01]; + let res: Option = input.span().from_be_bytes_partial(); + + assert!(res.is_none()); + } + + #[test] + fn test_u32_from_le_bytes() { + let input: Array = array![0x62, 0x15, 0x32, 0xf4]; + let res: Option = input.span().from_le_bytes(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xf4321562); + } + + #[test] + fn test_u32_from_le_bytes_too_big() { + let input: Array = array![0x62, 0x15, 0x32, 0xf4, 0x01]; + let res: Option = input.span().from_le_bytes(); + + assert!(res.is_none()); + } + + #[test] + fn test_u32_from_le_bytes_too_small() { + let input: Array = array![0x62, 0x15, 0x32]; + let res: Option = input.span().from_le_bytes(); + + assert!(res.is_none()); + } + + #[test] + fn test_u32_from_le_bytes_zero() { + let input: Array = array![0x00, 0x00, 0x00, 0x00]; + let res: Option = input.span().from_le_bytes(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0); + } + + #[test] + fn test_u32_from_le_bytes_max() { + let input: Array = array![0xff, 0xff, 0xff, 0xff]; + let res: Option = input.span().from_le_bytes(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xffffffff); + } + + #[test] + fn test_u32_from_le_bytes_partial() { + let input: Array = array![0x62, 0x15, 0x32]; + let res: Option = input.span().from_le_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0x321562); + } + + #[test] + fn test_u32_from_le_bytes_partial_full() { + let input: Array = array![0x62, 0x15, 0x32, 0xf4]; + let res: Option = input.span().from_le_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xf4321562); + } + + #[test] + fn test_u32_from_le_bytes_partial_too_big() { + let input: Array = array![0x62, 0x15, 0x32, 0xf4, 0x01]; + let res: Option = input.span().from_le_bytes_partial(); + + assert!(res.is_none()); + } + + #[test] + fn test_u32_from_le_bytes_partial_empty() { + let input: Array = array![]; + let res: Option = input.span().from_le_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0); + } + + #[test] + fn test_u32_from_le_bytes_partial_single_byte() { + let input: Array = array![0xff]; + let res: Option = input.span().from_le_bytes_partial(); + + assert!(res.is_some()); + assert_eq!(res.unwrap(), 0xff); + } + + #[test] + fn test_u32_to_bytes_full() { + let input: u32 = 0xf4321562; + let res: Span = input.to_be_bytes(); + + assert_eq!(res, [0xf4, 0x32, 0x15, 0x62].span()); + } + + #[test] + fn test_u32_to_bytes_partial() { + let input: u32 = 0xf43215; + let res: Span = input.to_be_bytes(); + + assert_eq!(res.len(), 3); + assert_eq!(*res[0], 0xf4); + assert_eq!(*res[1], 0x32); + assert_eq!(*res[2], 0x15); + } + + + #[test] + fn test_u32_to_bytes_leading_zeros() { + let input: u32 = 0x001234; + let res: Span = input.to_be_bytes(); + + assert_eq!(res.len(), 2); + assert_eq!(*res[0], 0x12); + assert_eq!(*res[1], 0x34); + } + + #[test] + fn test_u32_to_be_bytes_padded() { + let input: u32 = 7; + let result = input.to_be_bytes_padded(); + let expected = [0x0, 0x0, 0x0, 7].span(); + + assert_eq!(result, expected); + } + + #[test] + fn test_u32_to_le_bytes_full() { + let input: u32 = 0xf4321562; + let res: Span = input.to_le_bytes(); + + assert_eq!(res, [0x62, 0x15, 0x32, 0xf4].span()); + } + + #[test] + fn test_u32_to_le_bytes_partial() { + let input: u32 = 0xf43215; + let res: Span = input.to_le_bytes(); + + assert_eq!(res.len(), 3); + assert_eq!(*res[0], 0x15); + assert_eq!(*res[1], 0x32); + assert_eq!(*res[2], 0xf4); + } + + #[test] + fn test_u32_to_le_bytes_leading_zeros() { + let input: u32 = 0x00f432; + let res: Span = input.to_le_bytes(); + + assert_eq!(res.len(), 2); + assert_eq!(*res[0], 0x32); + assert_eq!(*res[1], 0xf4); + } + + #[test] + fn test_u32_to_le_bytes_padded() { + let input: u32 = 7; + let result = input.to_le_bytes_padded(); + let expected = [7, 0x0, 0x0, 0x0].span(); + + assert_eq!(result, expected); + } + + #[test] + fn test_u32_bytes_used() { + assert_eq!(0x00_u32.bytes_used(), 0); + let mut value: u32 = 0xff; + let mut i = 1; + loop { + assert_eq!(value.bytes_used(), i); + if i == 4 { + break; + }; + i += 1; + value = value.shl(8); + }; + } + + #[test] + fn test_u32_bytes_used_leading_zeroes() { + let len: u32 = 0x001234; + let bytes_count = len.bytes_used(); + + assert_eq!(bytes_count, 2); + } + } + + mod u64_test { + use crate::math::Bitshift; + use crate::traits::bytes::{ToBytes}; + use super::super::{BitsUsed, BytesUsedTrait, U64Trait}; + + + #[test] + fn test_u64_bytes_used() { + assert_eq!(0x00_u64.bytes_used(), 0); + let mut value: u64 = 0xff; + let mut i = 1; + loop { + assert_eq!(value.bytes_used(), i); + if i == 8 { + break; + }; + i += 1; + value = value.shl(8); + }; + } + + #[test] + fn test_u64_to_be_bytes_padded() { + let input: u64 = 7; + let result = input.to_be_bytes_padded(); + let expected = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 7].span(); + + assert_eq!(result, expected); + } + + #[test] + fn test_u64_trailing_zeroes() { + /// bit len is 3, and trailing zeroes are 2 + let input: u64 = 4; + let result = input.count_trailing_zeroes(); + let expected = 2; + + assert_eq!(result, expected); + } + + + #[test] + fn test_u64_leading_zeroes() { + /// bit len is 3, and leading zeroes are 64 - 3 = 61 + let input: u64 = 7; + let result = input.count_leading_zeroes(); + let expected = 61; + + assert_eq!(result, expected); + } + + #[test] + fn test_u64_bits_used() { + let input: u64 = 7; + let result = input.bits_used(); + let expected = 3; + + assert_eq!(result, expected); + } + + #[test] + fn test_u64_to_le_bytes_full() { + let input: u64 = 0xf432156278901234; + let res: Span = input.to_le_bytes(); + + assert_eq!(res, [0x34, 0x12, 0x90, 0x78, 0x62, 0x15, 0x32, 0xf4].span()); + } + + #[test] + fn test_u64_to_le_bytes_partial() { + let input: u64 = 0xf43215; + let res: Span = input.to_le_bytes(); + + assert_eq!(res, [0x15, 0x32, 0xf4].span()); + } + + #[test] + fn test_u64_to_le_bytes_padded() { + let input: u64 = 0xf43215; + let res: Span = input.to_le_bytes_padded(); + + assert_eq!(res, [0x15, 0x32, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00].span()); + } + } + + mod u128_test { + use core::num::traits::Bounded; + use crate::math::Bitshift; + use crate::traits::bytes::{ToBytes}; + use super::super::{BitsUsed, BytesUsedTrait}; + + #[test] + fn test_u128_bytes_used() { + assert_eq!(0x00_u128.bytes_used(), 0); + let mut value: u128 = 0xff; + let mut i = 1; + loop { + assert_eq!(value.bytes_used(), i); + if i == 16 { + break; + }; + i += 1; + value = value.shl(8); + }; + } + + #[test] + fn test_u128_to_bytes_full() { + let input: u128 = Bounded::MAX; + let result: Span = input.to_be_bytes(); + let expected = [ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 + ].span(); + + assert_eq!(result, expected); + } + + #[test] + fn test_u128_to_bytes_partial() { + let input: u128 = 0xf43215; + let result: Span = input.to_be_bytes(); + let expected = [0xf4, 0x32, 0x15].span(); + + assert_eq!(result, expected); + } + + #[test] + fn test_u128_to_bytes_padded() { + let input: u128 = 0xf43215; + let result: Span = input.to_be_bytes_padded(); + let expected = [ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf4, 0x32, 0x15 + ].span(); + + assert_eq!(result, expected); + } + + #[test] + fn test_u128_to_le_bytes_full() { + let input: u128 = 0xf432156278901234deadbeefcafebabe; + let res: Span = input.to_le_bytes(); + + assert_eq!( + res, + [ + 0xbe, + 0xba, + 0xfe, + 0xca, + 0xef, + 0xbe, + 0xad, + 0xde, + 0x34, + 0x12, + 0x90, + 0x78, + 0x62, + 0x15, + 0x32, + 0xf4 + ].span() + ); + } + + #[test] + fn test_u128_to_le_bytes_partial() { + let input: u128 = 0xf43215; + let res: Span = input.to_le_bytes(); + + assert_eq!(res, [0x15, 0x32, 0xf4].span()); + } + + #[test] + fn test_u128_to_le_bytes_padded() { + let input: u128 = 0xf43215; + let res: Span = input.to_le_bytes_padded(); + + assert_eq!( + res, + [ + 0x15, + 0x32, + 0xf4, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 + ].span() + ); + } + } + + mod u256_test { + use crate::math::Bitshift; + use crate::traits::bytes::{ToBytes}; + use crate::traits::integer::{U256Trait}; + use super::super::{BitsUsed, BytesUsedTrait}; + + #[test] + fn test_reverse_bytes_u256() { + let value: u256 = 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000; + let res = value.reverse_endianness(); + assert( + res == 0x0000450000DEFA0000200400000000ADDE00000077000000E5000000FFFFFFFA, + 'reverse mismatch' + ); + } + + #[test] + fn test_split_u256_into_u64_little() { + let value: u256 = 0xFAFFFFFF000000E500000077000000DEAD0000000004200000FADE0000450000; + let ((high_h, low_h), (high_l, low_l)) = value.split_into_u64_le(); + assert_eq!(high_h, 0xDE00000077000000); + assert_eq!(low_h, 0xE5000000FFFFFFFA); + assert_eq!(high_l, 0x0000450000DEFA00); + assert_eq!(low_l, 0x00200400000000AD); + } + + #[test] + fn test_u256_bytes_used() { + assert_eq!(0x00_u256.bytes_used(), 0); + let mut value: u256 = 0xff; + let mut i = 1; + loop { + assert_eq!(value.bytes_used(), i); + if i == 32 { + break; + }; + i += 1; + value = value.shl(8); + }; + } + + #[test] + fn test_u256_leading_zeroes() { + /// bit len is 3, and leading zeroes are 256 - 3 = 253 + let input: u256 = 7; + let result = input.count_leading_zeroes(); + let expected = 253; + + assert_eq!(result, expected); + } + + #[test] + fn test_u256_bits_used() { + let input: u256 = 7; + let result = input.bits_used(); + let expected = 3; + + assert_eq!(result, expected); + } + + #[test] + fn test_u256_to_be_bytes_full() { + let input: u256 = 0xf432156278901234deadbeefcafebabe0123456789abcdef0fedcba987654321; + let res: Span = input.to_be_bytes(); + + assert_eq!( + res, + [ + 0xf4, + 0x32, + 0x15, + 0x62, + 0x78, + 0x90, + 0x12, + 0x34, + 0xde, + 0xad, + 0xbe, + 0xef, + 0xca, + 0xfe, + 0xba, + 0xbe, + 0x01, + 0x23, + 0x45, + 0x67, + 0x89, + 0xab, + 0xcd, + 0xef, + 0x0f, + 0xed, + 0xcb, + 0xa9, + 0x87, + 0x65, + 0x43, + 0x21 + ].span() + ); + } + + #[test] + fn test_u256_to_be_bytes_partial() { + let input: u256 = 0xf43215; + let res: Span = input.to_be_bytes(); + + assert_eq!(res, [0xf4, 0x32, 0x15].span()); + } + + #[test] + fn test_u256_to_be_bytes_padded() { + let input: u256 = 0xf43215; + let res: Span = input.to_be_bytes_padded(); + + assert_eq!( + res, + [ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xf4, + 0x32, + 0x15 + ].span() + ); + } + + #[test] + fn test_u256_to_le_bytes_full() { + let input: u256 = 0xf432156278901234deadbeefcafebabe0123456789abcdef0fedcba987654321; + let res: Span = input.to_le_bytes(); + + assert_eq!( + res, + [ + 0x21, + 0x43, + 0x65, + 0x87, + 0xa9, + 0xcb, + 0xed, + 0x0f, + 0xef, + 0xcd, + 0xab, + 0x89, + 0x67, + 0x45, + 0x23, + 0x01, + 0xbe, + 0xba, + 0xfe, + 0xca, + 0xef, + 0xbe, + 0xad, + 0xde, + 0x34, + 0x12, + 0x90, + 0x78, + 0x62, + 0x15, + 0x32, + 0xf4 + ].span() + ); + } + + #[test] + fn test_u256_to_le_bytes_partial() { + let input: u256 = 0xf43215; + let res: Span = input.to_le_bytes(); + + assert_eq!(res, [0x15, 0x32, 0xf4].span()); + } + + #[test] + fn test_u256_to_le_bytes_padded() { + let input: u256 = 0xf43215; + let res: Span = input.to_le_bytes_padded(); + + assert_eq!( + res, + [ + 0x15, + 0x32, + 0xf4, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 + ].span() + ); + } + } +} diff --git a/cairo/kakarot-ssj/crates/utils/src/utils.cairo b/cairo/kakarot-ssj/crates/utils/src/utils.cairo new file mode 100644 index 000000000..242b45b83 --- /dev/null +++ b/cairo/kakarot-ssj/crates/utils/src/utils.cairo @@ -0,0 +1,309 @@ +use core::num::traits::Zero; + +/// Pack an array of bytes into 31-byte words and a final word. +/// The bytes are packed in big-endian order. +/// +/// # Arguments +/// * `input` - An array of bytes to pack. +/// +/// # Returns +/// An array of felt252 values containing the packed bytes. The last word might not be full. +pub fn pack_bytes(input: Span) -> Array { + let mut result: Array = Default::default(); + let mut current_word: u256 = 0; + let mut byte_count: usize = 0; + + for byte in input { + current_word = (current_word * 256) + (*byte).into(); + byte_count += 1; + + if byte_count == 31 { + result.append(current_word.try_into().unwrap()); + current_word = 0; + byte_count = 0; + } + }; + + // Append the last word if there are any remaining bytes + if byte_count != 0 { + result.append(current_word.try_into().unwrap()); + } + + result +} + +/// Load packed bytes from an array of bytes packed in 31-byte words and a final word. +/// +/// # Arguments +/// +/// * `input` - An array of 31-bytes words and a final word. +/// * `bytes_len` - The total number of bytes to unpack. +/// +/// # Returns +/// +/// An `Array` containing the unpacked bytes in big-endian order. +/// +/// # Performance considerations +/// +/// This function uses head-recursive helper functions (`unpack_chunk`) for unpacking individual +/// felt252 values. Head recursion is used here instead of loops because the Array type in Cairo is +/// append-only. This approach allows us to append the bytes in the correct order (big-endian) +/// without needing to reverse the array afterwards. This leads to more efficient memory usage and +/// performance. +pub fn load_packed_bytes(input: Span, bytes_len: u32) -> Array { + if input.is_empty() { + return Default::default(); + } + let (chunk_counts, remainder) = DivRem::div_rem(bytes_len, 31); + let mut res: Array = Default::default(); + + for i in 0 + ..chunk_counts { + let mut value: u256 = (*input[i]).into(); + unpack_chunk(value, 31, ref res); + }; + + if remainder.is_zero() { + return res; + } + unpack_chunk((*input[input.len() - 1]).into(), remainder, ref res); + res +} + + +/// Unpacks only a specified number of bytes from the value. Uses head recursion to append bytes in +/// big-endian order. +/// +/// # Arguments +/// +/// * `value` - The u256 value to unpack. +/// * `remaining_bytes` - The number of bytes to unpack from the value. +/// * `output` - The array to append the unpacked bytes to. +fn unpack_chunk(value: u256, remaining_bytes: u32, ref output: Array) { + if remaining_bytes == 0 { + return; + } + let (q, r) = DivRem::div_rem(value, 256); + unpack_chunk(q, remaining_bytes - 1, ref output); + output.append(r.try_into().unwrap()); +} + +#[cfg(test)] +mod tests { + use super::{load_packed_bytes, pack_bytes}; + + #[test] + fn test_should_load_empty_array() { + let res = load_packed_bytes([].span(), 0); + + assert_eq!(res.span(), [].span()); + } + + #[test] + fn test_should_load_zeroes() { + let input = [0x00, 0x00]; + let res = load_packed_bytes(input.span(), 35); + + assert_eq!(res.span(), [0x00; 35].span()); + } + + #[test] + fn test_should_load_single_31bytes_felt() { + let input = [0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff]; + let res = load_packed_bytes(input.span(), 31); + + assert_eq!(res.span(), [0xff; 31].span()); + } + + #[test] + fn test_should_load_with_non_full_last_felt() { + let input = [0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0xffff]; + let res = load_packed_bytes(input.span(), 33); + + assert_eq!(res.span(), [0xff; 33].span()); + } + + #[test] + fn test_should_load_multiple_words() { + let input = [ + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffff + ]; + let res = load_packed_bytes(input.span(), 64); + + assert_eq!(res.span(), [0xff; 64].span()); + } + + #[test] + fn test_should_load_mixed_byte_values_big_endian() { + let input = [0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e, 0x1f2021]; + let res = load_packed_bytes(input.span(), 34); + assert_eq!( + res.span(), + [ + 0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21 + ].span() + ); + } + + #[test] + fn test_should_pack_empty_array() { + let res = pack_bytes([].span()); + + assert_eq!(res, array![]); + } + + #[test] + fn test_should_pack_single_31bytes_felt() { + let input = [0xff; 31]; + let res = pack_bytes(input.span()); + + assert_eq!(res, array![0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff]); + } + + #[test] + fn test_should_pack_with_non_full_last_felt() { + let mut input = [0xff; 33]; + let res = pack_bytes(input.span()); + + assert_eq!( + res, array![0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0xffff] + ); + } + + #[test] + fn test_should_pack_multiple_words() { + let input = [0xff; 64]; + let res = pack_bytes(input.span()); + + assert_eq!( + res, + array![ + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, + 0xffff + ] + ); + } + + #[test] + fn test_should_pack_mixed_byte_values_big_endian() { + let input = [ + 0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21 + ]; + let res = pack_bytes(input.span()); + assert_eq!( + res, array![0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e, 0x1f2021] + ); + } + + #[test] + fn test_pack_and_unpack_roundtrip() { + let original = [ + 0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21 + ].span(); + let packed = pack_bytes(original); + let unpacked = load_packed_bytes(packed.span(), original.len().try_into().unwrap()); + assert_eq!(original, unpacked.span()); + } +} diff --git a/cairo/kakarot-ssj/docs/CODE_OF_CONDUCT.md b/cairo/kakarot-ssj/docs/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..3e16aaa94 --- /dev/null +++ b/cairo/kakarot-ssj/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,78 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and our +community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and +expression, level of experience, education, socioeconomic status, nationality, +personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project email address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project maintainer using any of the +[private contact addresses](https://github.com/sayajin-labs/kakarot-ssj#support). +All complaints will be reviewed and investigated and will result in a response +that is deemed necessary and appropriate to the circumstances. The project team +is obligated to maintain confidentiality with regard to the reporter of an +incident. Further details of specific enforcement policies may be posted +separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant](https://www.contributor-covenant.org), version 1.4, +available at + + +For answers to common questions about this code of conduct, see + diff --git a/cairo/kakarot-ssj/docs/CONTRIBUTING.md b/cairo/kakarot-ssj/docs/CONTRIBUTING.md new file mode 100644 index 000000000..526923a4b --- /dev/null +++ b/cairo/kakarot-ssj/docs/CONTRIBUTING.md @@ -0,0 +1,128 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish +to make via issue, email, or any other method with the owners of this repository +before making a change. Please note we have a +[code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions +with the project. + +## Contributing guidelines + +Kakarot is an open-source project and we welcome contributions of all kinds. +However, we ask that you follow these guidelines when contributing: + +- If you have an idea for a new feature or a bug fix, please create an issue + first. This will allow us to discuss the idea and provide feedback before you + start working on it. +- If you are working on an issue, please ask to be assigned. This will help us + keep track of who is working on what and avoid duplicated work. +- Do not ask to be assigned to an issue if you are not planning to work on it + immediately. We want to keep the project backlog clean and organized. If you + are interested in working on an issue, please comment on the issue and we will + assign it to you. We will remove the assignment if you do not start working on + the issue within 2 days. You can, of course, submit a draft PR if you need + more time or have questions regarding the issue. +- Prefer rebasing over merging when updating your PR. This will keep the commit + history clean and make it easier to review your changes. +- Adopt + [conventional commit messages](https://www.conventionalcommits.org/en/v1.0.0/). + This will help us when reviewing your PR. + +## Prerequisites + +To set up a development environment, please follow the steps from +[the README.md](../README.md#installation). Make sure that your Scarb version +matches the one expected, specified in the [.tool-versions](../.tool-versions) +file. + +## Issues and feature requests + +You've found a bug in the source code, a mistake in the documentation or maybe +you'd like a new feature?Take a look at +[GitHub Discussions](https://github.com/sayajin-labs/kakarot-ssj/discussions) to +see if it's already being discussed. You can help us by +[submitting an issue on GitHub](https://github.com/sayajin-labs/kakarot-ssj/issues). +Before you create an issue, make sure to search the issue archive -- your issue +may have already been addressed! + +Please try to create bug reports that are: + +- _Reproducible._ Include steps to reproduce the problem. +- _Specific._ Include as much detail as possible: which version, what + environment, etc. +- _Unique._ Do not duplicate existing opened issues. +- _Scoped to a Single Bug._ One bug per report. + +**Even better: Submit a pull request with a fix or new feature!** + +### How to submit a Pull Request + +1. Search our repository for open or closed + [Pull Requests](https://github.com/sayajin-labs/kakarot-ssj/pulls) that + relate to your submission. You don't want to duplicate effort. +2. Fork the project +3. Create your feature branch (`git checkout -b feat/amazing_feature`) +4. Commit your changes (`git commit -m 'feat: add amazing_feature'`) +5. Push to the branch (`git push origin feat/amazing_feature`) +6. [Open a Pull Request](https://github.com/sayajin-labs/kakarot-ssj/compare?expand=1) + +### Migrating from Cairo Zero to Cairo + +Kakarot SSJ is a rewrite of +[Kakarot Zero](https://github.com/kkrt-labs/kakarot), an implementation of the +Ethereum Virtual Machine in Cairo Zero. As such, most logic has already been +written. As part of the migration path, this business logic will be either +ported and translated as is or improved. + +Here is a quick checklist when building on Kakarot SSJ and taking on issues. + +#### Working on Opcodes + +When working on opcodes, make sure to check several things: + +- The issue's specs, always start with the issue. +- The + [Ethereum yellow paper's](https://ethereum.github.io/yellowpaper/paper.pdf) + paragraph for the issue, there is a non-zero probability that the early + implementation missed a specific edge case. +- The [EVM playground](https://www.evm.codes/) to be able to read the specs and + play around directly on the [playground](https://www.evm.codes/playground). +- The [Cairo Zero implementation](https://github.com/kkrt-labs/kakarot) that + already exists in the above mentioned repo. + +Now, here are things to pay attention to: + +- The types: we **should avoid using felt252 type as much as possible**. In some + cases, enums, structs and trait might be a good idea to write more idiomatic + Cairo. +- The tests: we need extensive testing. Unit tests and integration tests. +- The gas: we need our code to be lean. When possible, test different ways to + implement the same feature and argue which one is least gas expensive. But be + careful, _first make it work, then make it fast_. No need to over-engineer and + prematurely optimise. + +#### Working on utils + +When working on test utils, script & practical helpers, remember to: + +- Check if the util is still relevant, a lot of new features make Cairo very + powerful and make old utils obsolete. +- Check if the utility function can be refactored into a trait for a specific + type, e.g. as + [per this PR](https://github.com/kkrt-labs/kakarot-ssj/pull/74/files#diff-888cfc6a9147d3727c6f8c083b5d0890ed686240e5dc4da1a741e025bdbd81f7R282) +- Check if the type is still relevant, don't forget: we **should avoid using + felt252 type as much as possible** and **use unsigned integers as much as + possible**. + +#### Working on data structures + +Kakarot has many data structures, e.g. an Ethereum Transaction (struct), a Stack +(Cairo dict), a Memory (Cairo dict), etc. When porting over the data structures, +pay attention: + +- Should it be a struct? +- Should it be an enum: this is a new type made available in Cairo. +- Which types to use? Remember! **use unsigned integers as much as possible**. +- Remember to add traits for specific types instead of utils to write Cairo (& + Rust) idiomatic code. +- Test everything! Even small traits for specific types. diff --git a/cairo/kakarot-ssj/docs/SECURITY.md b/cairo/kakarot-ssj/docs/SECURITY.md new file mode 100644 index 000000000..1882c7e51 --- /dev/null +++ b/cairo/kakarot-ssj/docs/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Reporting a Vulnerability + +If there are any vulnerabilities in **Kakarot**, don't hesitate to _report +them_. + +1. Use any of the + [private contact addresses](https://github.com/sayajin-labs/kakarot-ssj#support). +2. Describe the vulnerability. + + If you have a fix, that is most welcome -- please attach or summarize it in + your message! + +3. We will evaluate the vulnerability and, if necessary, release a fix or + mitigating steps to address it. We will contact you to let you know the + outcome, and will credit you in the report. + + Please **do not disclose the vulnerability publicly** until a fix is + released! + +4. Once we have either a) published a fix, or b) declined to address the + vulnerability for whatever reason, you are free to publicly disclose it. diff --git a/cairo/kakarot-ssj/docs/general/account_state.png b/cairo/kakarot-ssj/docs/general/account_state.png new file mode 100644 index 000000000..0df32c476 Binary files /dev/null and b/cairo/kakarot-ssj/docs/general/account_state.png differ diff --git a/cairo/kakarot-ssj/docs/general/contract_bytecode.md b/cairo/kakarot-ssj/docs/general/contract_bytecode.md new file mode 100644 index 000000000..f2155df15 --- /dev/null +++ b/cairo/kakarot-ssj/docs/general/contract_bytecode.md @@ -0,0 +1,230 @@ +# Bytecode Storage Methods for Kakarot on Starknet + +The bytecode is the compiled version of a contract, and it is what the Kakarot +EVM will execute when a contract is called. As Kakarot's state is embedded into +the Starknet chain it is deployed on, contracts are not actually "deployed" on +Kakarot: instead, the EVM bytecode of the deployed contract is first executed, +and the returned data is then stored on-chain at a particular storage address +inside the Starknet contract corresponding to the contract's EVM address, whose +address is deterministically computed. The Kakarot EVM will be able to load this +bytecode by querying the storage of this Starknet contract when a user interacts +with its associated EVM address. + +```mermaid +flowchart TD + A[RPC call] --> B["eth_sendTransaction"] + B --> |Check transaction type| C{Is deploy transaction?} + C -- Yes --> D1[Execute initialization code] + D1 -->|Set account code to return data| E1[Commit code to Starknet storage] + E1 --> F1[Return deployed contract address] + + C -- No --> D2[Load account code from KakarotCore storage] + D2 --> E2[Execute bytecode] + E2 --> F2[Return execution result] +``` + + Transaction flow for deploy and execute transactions in +Kakarot + +There are several different ways to store the bytecode of a contract, and this +document will provide a quick overview of the different options, to choose the +most optimized one for this use case. The three main ways of handling contract +bytecode that were considered are: + +- Storing the bytecode inside a contract's storage, using Ethereum as an L1 data + availability layer. +- Storing the bytecode inside a contract's storage, using another data + availability layer. +- Storing the bytecode directly in the contract code, not as a part of the + contract's storage. + +These three solutions all have their respective pros and cons, which will be +discussed in the following sections. + +## Foreword: Data availability + +In Validity Rollups, verifying the validity proof on L1 is sufficient to +guarantee the validity of a transaction execution on L2, without needing the +detailed transaction information to be sent to Ethereum. + +However, to allow independent verification of the L2 chain's state and prevent +malicious operators from censoring or freezing the chain, some amount of data is +still required to be posted on a Data Availability (DA) layer. This makes the +Starknet state available even if the operator suddenly ceases operations. Data +availability ensures that users can always reconstruct the state of the rollup +by deriving its current state from the data posted by the rollup operator. + +Without this, users would not be able to query an L2 contract's state if the +operator becomes unavailable. It provides users the security of knowing that if +the Starknet sequencer ever stops functioning, they can prove custody of their +funds using the data posted on the DA Layer. If that DA Layer is Ethereum +itself, then Ethereum's security guarantees are inherited. + +## Different approaches to storing contract bytecode + +### Using Ethereum as a DA Layer + +Starknet currently uses Ethereum as its DA Layer. Each state update verified +on-chain is accompanied by the state diff between the previous and new state, +sent as calldata to Ethereum. This allows anyone observing Ethereum to +reconstruct the current state of Starknet. This security comes with a +significant price, as the publication of state diffs on Ethereum accounted for +[over 93% of the transaction fees paid on Starknet](https://community.starknet.io/t/volition-hybrid-data-availability-solution/97387). + +The first choice when storing contract bytecode is to store it as a regular +variable in the contract account's storage, with its state diff posted on +Ethereum acting as the DA Layer. + +In this case, the following data would reach L1: + +- The Starknet address of the contract account +- The number of updated keys in that contract +- The keys to update +- The new values for these keys + +On Starknet, the associated storage update fee for a transaction updating $n$ +unique contracts and $m$ unique keys is: + +$$ gas\ price \cdot c_w \cdot (2n + 2m) $$ + +where $c_w$ is the calldata cost (in gas) per 32-byte word. + +When storing the EVM bytecode during deployment, one single contract (the +Starknet contract corresponding to the ContractAccount) would be updated, with +$m$ keys, where $m = (B / 31) + 2$ and $B$ is the size of the bytecode to store +(see [implementation details](./contract_bytecode.md#implementation-details)). + +Considering a gas price of 34 gwei (average gas price in 2023, according to +[Etherscan](https://etherscan.io/chart/gasprice)), a calldata cost of 16 per +non-zero byte of calldata and the size of a typical ERC20 contract size of 2174 +bytes, we would have $m = 72$. The associated storage update fee would be: + +$$ fee = 34 \cdot (16 \cdot 32) \cdot (2 + 144) = 2,541,468 \text{ gwei}$$ + +This is the solution that was chosen for Kakarot; but there are other options +that could be considered presented thereafter. + +### Using Starknet's volition mechanism + +Volition is a hybrid data availability solution, providing the ability to choose +the data availability layer used for contract data. It allows users to choose +between using Ethereum as a DA Layer, or using Starknet itself as a DA Layer. +The security of state transitions, verified by STARK proofs on L1, is the same +for both L2 and L1 data availability modes. The difference is in the data +availability guarantees. When a state transition is verified on L1, its +correctness is ensured - however, the actual state of the L2 is not known on L1. +By posting state diffs on L1, the current state of Starknet can be reconstructed +from the beginning, but this has a significant cost as mentioned previously. + +![Volition](volition.png) + +Volition will allow developers to choose whether data will be stored in L1DA or +L2DA mode. This makes it possible to store data on L2, which is much less +expensive than storing it on L1. Depending on the data stored, it can be +advantageous if the cost of storing it on L1 is higher than its intrinsic value. +For example, a Volition-ERC20 token standard could have two balances - one on +L1DA for maximal security (major assets), and one on L2DA for lower +security/fees (small transactions). + +In this case, the contract bytecode could be stored in a storage variable +settled on L2DA instead of L1DA. This would make Kakarot contract deployment +extremely cheap, by avoiding the cost of posting bytecode state diffs to +Ethereum. + +#### Associated Risks + +Some risks must be considered when using Volition. If a majority of malicious +sequencers collude and decide to not share an L2DA change with other +sequencers/full nodes, once the attack ends, the honest sequencers won't have +the data to reconstruct and compute the new L2DA root. In this case, not only is +the L2DA inaccessible, but any execution relying on L2DA will become unprovable, +since sequencers lack the correct L2DA state. + +While unlikely, this remains a possibility to consider since L2DA is less secure +than L1DA. If it happened, the stored bytecode would be lost and the deployed +contract unexecutable. + +> Note: While Volition could potentially store bytecode on L2DA in the future, +> this is not currently possible as Volition is not yet implemented on Starknet. + +### Storing the EVM bytecode in the Cairo contract code + +The last option is to store the EVM bytecode directly in the Cairo contract +code. This has the advantage of also being cheap, as this data is not stored on +L1. + +On Starknet, there is a distinction between classes which is the definition of a +contract containing the Cairo bytecode, and contracts which are instances of +classes. When you declare a contract on Starknet, its information is added to +the +[Classes Tree](https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/starknet-state/#classes_tree), +which encodes information about the existing classes in the state of Starknet by +mapping class hashes to their compiled class hash. This class tree is itself a +part of the Starknet State Commitment, which is verified on Ethereum during +state updates. The class itself is stored in the nodes (both sequencers and full +nodes) of Starknet. + +To implement this, a new class would need to be declared each time a +ContractAccount is deployed. This class would contain the contract's EVM +bytecode, exposed via a view function returning the bytecode. To do this, the +RPC would need to craft a custom Starknet contract containing the EVM bytecode +in its source code, and declare it on Starknet - not ideal for security and +practicality. + +## Implementation details + +Kakarot uses the first solution, storing bytecode in a storage variable +committed to Ethereum. This solution is the most secure one, as it relies on +Ethereum as a DA Layer, and thus inherits from Ethereum's security guarantees, +ensuring that the bytecode of the deployed contract is always available. + +In Ethereum, a `deploy` transaction is identified by a null `to` address +(`Option::None`). The calldata sent to the KakarotCore contract when deploying a +new contract will be passed as an `Array` to the `eth_send_transaction` +entrypoint of the KakarotCore contract. This bytecode will then be packed 31 +bytes at a time, reducing by 31 the size of the bytecode stored in storage, +which is the most expensive part of the transaction. + +The contract storage related to a deployed contract is organized as: + +```rust +struct Storage { + bytecode: List, + pending_word: felt252, + pending_word_len: usize, +} +``` + +We use the `List` type from the +[Alexandria](https://github.com/keep-starknet-strange/alexandria/blob/main/src/storage/src/list.cairo) +library to store the bytecode, allowing us to store up to 255 31-bytes values +per `StorageBaseAddress`. Indeed, the current limitation on the maximal size of +a complex storage value is 256 field elements, where a field element is the +native data type of the Cairo VM. If we want to store more than 256 field +elements, which is the case for bytecode larger than 255 31-bytes values, which +represents 7.9kB, we need to split the data between multiple storage addresses. +The `List` type abstracts this process by automatically calculating the next +storage address to use, by applying poseidon hashes on the base storage address +of the list with the index of the segment to store the element in. + +The logic behind this storage design is to make it very easy to load the +bytecode in the EVM when we want to execute a program. We will rely on the +ByteArray type, which is a type from the core library that we can use to access +individual byte indexes in an array of packed bytes31 values. + +The rationale behind this structure is thoroughly documented in the core library +code. The variable stored in our contract's storage reflect the fields of the +ByteArray type. Once our bytecode is written in storage, we can simply load it +with + +```rust + let bytecode = ByteArray { + data: self.bytecode.read().array(), + pending_word: self.pending_word.read(), + pending_word_len: self.pending_word_len.read() +}; +``` + +After which the value of the bytecode at offset `i` can be accessed by simply +doing `bytecode[i]` when executing the bytecode instructions in the EVM - making +it convenient to iterate over. diff --git a/cairo/kakarot-ssj/docs/general/contract_storage.md b/cairo/kakarot-ssj/docs/general/contract_storage.md new file mode 100644 index 000000000..b77599022 --- /dev/null +++ b/cairo/kakarot-ssj/docs/general/contract_storage.md @@ -0,0 +1,274 @@ +# Kakarot Storage + +## Storage in Ethereum + +The top-level data structure that holds information about the state of the +Ethereum blockchain is called the _world state_, and is a mapping of Ethereum +addresses (160-bit values) to accounts. Each Ethereum address represents an +account composed by a _nonce_, an _ether balance_, a _storage_, and a _code_. We +make the distinction between EOA (Externally Owned Accounts) that have no code +and an empty storage, and contracts that can have code and storage. + +![Account state](account_state.png) + +_Account state associated to an Ethereum address. Source: +[EVM Illustrated](https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf)_ + +In traditional EVM clients, like Geth, the _world state_ is stored as a _trie_, +and information about account are stored in the world state trie and can be +retrieved through queries. Each account in the world state trie is associated +with an account storage trie, which stores all of the information related to the +account. When Geth updates the storage of a contract by executing the SSTORE +opcodes, it does the following: + +- It updates the `value` associated to a `key` of the storage of a contract + deployed at a specific `address`. However, it updates a `dirtyStorage`, which + refers to storage entries that have been modified in the current transaction + execution. +- It tracks the storage modifications in a `journal` so that it can be reverted + in case of a revert opcode or an exception in the transaction execution. +- At the end of the execution of a transaction, all dirty storage slots are + copied across to `pendingStorage`, which in turn is copied across to + `originStorage` when the trie is finally updated. This effectively updates the + storage root of the account state. + +The behavior for the SLOAD opcode is very complementary to the SSTORE opcode. +When Geth executes the SLOAD opcode, it does the following: + +- It starts by doing a check on `dirtyStorage` to see if it contains a value for + the queried key, and returns it if so. +- Otherwise, it retrieves the value from the committed account storage trie. + +Since one transaction can access a storage slot multiple times, we must ensure +that the result returned is the most recent value. This is why Geth first checks +`dirtyStorage`, which is the most up-to-date state of the storage. + +```mermaid +flowchart TD; + A[Start: Run Bytecode] -->|SSTORE| B[Update value in dirtyStorage] + B --> C[Track modifications in journal] + C --> D[End of current execution] + D -->|Execution reverted| M[Clear dirtyStorage from entries in journal] + D -->|Execution successful| E[ ] + A -->|SLOAD| H[Check dirtyStorage for queried key] + H -->|Key found| I[Return value from dirtyStorage] + H -->|Key not found| J[Retrieve value from committed account storage trie] + J --> K[Return retrieved value] + style A fill:#DB5729,stroke:#333,stroke-width:2px; + style B fill:#296FDB,stroke:#333,stroke-width:2px; + style C fill:#296FDB,stroke:#333,stroke-width:2px; + style D fill:#296FDB,stroke:#333,stroke-width:2px; + style E fill:#296FDB,stroke:#333,stroke-width:2px; + style H fill:#136727,stroke:#333,stroke-width:2px; + style I fill:#136727,stroke:#333,stroke-width:2px; + style J fill:#136727,stroke:#333,stroke-width:2px; + style K fill:#136727,stroke:#333,stroke-width:2px; + style M fill:#DB2929,stroke:#333,stroke-width:2px; +``` + +_Simplified representation of SSTORE and SLOAD Opcodes +behavior in the Geth EVM Client_ + +## Storage in Kakarot + +As Kakarot is a contract that is deployed on Starknet and is not a client that +can directly manipulate a storage database, our approach differs from one of a +traditional client. We do not directly manipulate tries. Instead, we have access +to contracts storage on the Starknet blockchain, that we can query using +syscalls to read and update the value of a storage slot. + +There are two different ways of handling Storage in Kakarot. + +### One storage space per Kakarot Contract + +The first approach is to have one storage space per Kakarot contract. This means +that for every contract that is deployed on Kakarot, we will deploy an +underlying Starknet contract, which has its own state which can only be queried +by itself. + +This design closely resembles the design of the EVM. It has the following +properties: + +- The two different kinds of EVM accounts - Externally Owned Accounts (EOA) and + Contract Accounts (CA) - are both represented by Starknet smart contracts. + Each EVM account is mapped to a unique Starknet contract. Each contract + account has its own storage, and has external functions that can be called by + Kakarot to read and write to its storage. +- Each contract is deployed by Kakarot, and contains its own bytecode in storage + in the case of a smart contract (as EOAs don't have code) +- Each contract account has external functions that can be called by Kakarot to + read the bytecode it stores and to read / write to its storage. This makes + Kakarot an effective "admin" to all contracts with rights to modify their + storage. + +This design has some limitations: + +- We perform a `call_contract_syscall` for each SLOAD/SSTORE operation that is + committed to Starknet, which has an extra overhead compared to directly + modifying the current contract's storage. Given that only KakarotCore can + modify the storage of a Kakarot contract, we could directly store the whole + world state in the main Kakarot contract storage. +- It adds external entrypoints with admin rights to read and write from storage + in each Kakarot contract, which adds security risks. +- It moves away from the traditional EVM design, in which execution clients + store account states in a common database backend. + +However, it has some interesting properties. It allows us to have a one-to-one +mapping between Kakarot contracts and Starknet contracts, which makes it easier +to perform value transfers with the chain's native token. Moreover, it allows +one to send funds from a Starknet account to a Kakarot account, which can be +useful to implement a bridging mechanism to Kakarot with low overhead, or any +other mechanism that requires interacting with funds of a Kakarot account. + +Considering the compatibility properties, we will use this design in Kakarot. +The second design, presented after, still has some interesting properties that +we will discuss. But the benefits it brings do not outweigh the loss of +compatibility. + +### A shared storage space for all Kakarot Contracts + +The second approach is to have a unified storage space for all contract accounts +in the main Kakarot smart contract. While Kakarot is not a traditional Ethereum +Client, we can still use a design that is similar. Traditional clients hold a +state database in which the account states are stored. We can do the same, but +instead of storing the account states in a database, we store them in the +KakarotCore contract storage. Therefore, we do not need to deploy a Starknet +contract for each Kakarot account contract, which saves users costs related to +deploying contracts. + +A contract’s storage on Starknet is a persistent storage space where you can +read, write, modify, and persist data. The storage is a map with $2^{251}$ +slots, where each slot is a `felt252` which is initialized to 0. + +This new model doesn't expose read and write methods on Kakarot contracts. +Instead of having $n$ contracts with `write_storage` and `read_storage` +entrypoints, the only way to update the storage of a Kakarot contract is now +through executing SLOAD / SSTORE internally to KakarotCore. + +Regarding the security of such a design, we can reason about the probability of +a collision occurring when interacting with this shared storage. +[65M contracts](https://dune.com/queries/2284893/3744521) have been deployed on +Ethereum so far. If we assume that Kakarot could reach the same number of +contracts, that would leave us with a total of +$2^{251} / 65\cdot10^6 \approx 2^{225}$ slots per contract. Even with a +hypothetical number of 100 billion contracts, we would still have around +$2^{214}$ storage slots available per contract. + +Considering the birthday paradox, the probability of a collision occurring, +given $2^{214}$ randomly chosen slots, is roughly $1/2^{107}$. This is a very +low probability, which is considered secure by today's standards. We can +therefore consider that the collision risk is negligible and that this storage +layout doesn't introduce any security risk to Kakarot. For reference, Ethereum +has 80 bits of security on its account addresses, which are 160 bits long. + +But, as we're looking for maximum compatibility between "pure" Starknet +contracts and "Kakarot" Starknet contracts, this design is not ideal from a +compatibility perspective. It requires us to keep an internal accounting of the +balances of each account, and to expose external entrypoints in order to query +the balances of each account. This is not ideal, as it completely breaks the +compatibility with Starknet. + +### Tracking and reverting storage changes + +The storage mechanism presented in the [Local State](./local_state.md) section +enables us to revert storage changes by using a concept similar to Geth's +journal. Each storage change will be stored in a `StateChangeLog` implemented +using a `Felt252Dict` data structure, that will associate each modified storage +address to its new value. This allows us to perform three things: + +- When executing a transaction, instead of using one `storage_write_syscall` per + SSTORE opcode, we can simply store the storage changes in this cache state. At + the end of the transaction, we can finalize all the storage writes together + and perform only one `storage_write_syscall` per modified storage address. +- When reading from storage, we can first read from the state to see if the + storage slot has been modified. If it's the case, we can read the new value + from the state instead of performing a `storage_read_syscall`. +- If the transaction reverts, we won't need to revert the storage changes + manually. Instead, we can simply not finalize the storage changes present in + the state, which can save a lot of gas. + +```mermaid +sequenceDiagram + participant C as Caller + participant K as KakarotCore + participant M as Machine + participant J as State + participant S as ContractState + + C->>K: Executes Kakarot contract + K->>M: Executes Opcode (Either SSTORE or SLOAD) + + Note over K,M: If it's an SSTORE operation, it writes to Storage. + Note over K,M: If it's an SLOAD operation, it reads from Storage. + + alt SSTORE + M-->>M: hash = hash(evm_address, storage_key) + M->>J: state.accounts_storage.insert(hash, (key, value)) + else SLOAD + M-->>M: hash = hash(evm_address, storage_key) + M->>J: state.accounts_storage.get(hash) + J -->> M: Nullable~value~ + alt State returns value + + else State returns nothing + M->>S: storage_read(evm_address, key) + S-->>M: value + end + end + Note over K,M: Committing State entries to storage + K->>M: Commit + M->>J: Get all state storage entries + J -->>M: entries + loop for each storage entry + M->>S: storage_write(evm_address, key, value) + end + + Note over S: Storage is now updated with the final state of all changes made during the transaction. +``` + +Sequence of interactions between Kakarot, the local State +and Starknet for the SSTORE and SLOAD Opcodes + +### Implementation + +The SSTORE and SLOAD opcodes are implemented to first read and write to the +`State` instead of directly writing to the KakarotCore contract's storage. The +internal location of the storage slot is computed by applying the poseidon hash +on the EVM address of the contract and the storage key. We store the key +nonetheless, as it will be needed to finalize the storage updates at the end of +the transaction when retrieving the address of the contract to apply storage +changes to. + +Using the `storage_at` and `set_storage_at` entrypoints in the contract +accounts, we can arbitrarily read and write to another contract's storage. +Therefore, we will be able to simply implement the SSTORE and SLOAD opcodes in +two steps, as follows: + +```rust + // SSTORE + let hash = poseidon_hash(evm_address, key); + self.state.accounts_storage.write(hash, (evm_address, key, value)); +``` + +```rust + // SLOAD + let storage_address = poseidon_hash(evm_address, storage_slot); + let maybe_entry = self.accounts_storage.read(internal_key); + match maybe_entry { + Option::Some((_, key, value)) => { return Result::Ok(value); }, + Option::None => { + let account = self.get_account(evm_address); + return account.read_storage(key); + } +} +``` + +```rust + // Finalizing storage updates + for storage_hash in accounts_storage{ + let (contract_address, key, value) = account_state_keys[storage_hash] + ContractAccountDispatcher{contract_address}.set_storage_at(key, value); + } +``` + +> Note: these code snippets are in pseudocode, not valid Cairo code. diff --git a/cairo/kakarot-ssj/docs/general/local_state.md b/cairo/kakarot-ssj/docs/general/local_state.md new file mode 100644 index 000000000..038ca8cfb --- /dev/null +++ b/cairo/kakarot-ssj/docs/general/local_state.md @@ -0,0 +1,177 @@ +# Local State + +A challenge with the implementation of Kakarot is that since it lives as a +contract deployed on chain, it cannot always manipulate the underlying state of +the blockchain once it has performed modifications on it to revert the changes +made. For example, a deployed contract cannot delete itself from the blockchain, +and emitted events cannot be deleted. + +This means that before committing any changes to the blockchain, Kakarot must +ensure that the changes are valid. This is done by maintaining a local state +that is updated as the code is executed. + +The machine is created along with a default empty state. The state is a struct +that wraps dictionaries holding the values stored in the blockchain storage +modified locally. + +Because of technical limitations around the usage of dictionaries preventing us +from using dicts in self-referring data structures, instead of having one state +per execution context that we could merge into the parent state if the context +exits successfully or discard if the context reverts, we have one global `State` +structs that achieves the same behavior by tracking transactional changes, which +refers to the changes made inside the current transaction as a whole, and a +contextual changes, which refers to changes made inside the current execution +context. + +The local state is updated as the code is executed, and when an execution +context is finalized, we merge the contextual state updates into the +transactional state. When a transaction finishes, we apply the transactional +state diffs by writing them inside the blockchain storage. + +This design allows us to simulate reverting the changes made to the blockchain +storage by simply discarding the contextual state updates, and to commit the +changes by applying the transactional state updates. + +The sequence diagram below illustrates how Kakarot interacts with both Starknet +storage and its local cached state. + +```mermaid +sequenceDiagram + participant User + participant KakarotCore + participant LocalState + participant Machine + participant Starknet + + User->>+KakarotCore:Sends transaction + KakarotCore->>+Machine: Instantiates machine with bytecode to run + Machine->>LocalState: Initialize local state + Machine->>+LocalState: Start execution + + loop Executes EVM bytecode + Machine->>LocalState: Record contextual changes + end + + alt Execution Context Stops Successfully + + Machine->>LocalState: Merge contextual changes into transactional state + else Execution Context Reverts + Machine->>LocalState: Discard contextual changes + end + + Machine-->>-Machine: Execution ended + + alt Execution terminated successfully + KakarotCore->>Starknet: Apply transactional state diffs to Starknet + + else Execution failed + KakarotCore->>LocalState: Discard transactional changes + end + + KakarotCore-->>-User: Transaction result + +``` + +Interactions between Kakarot, its local state and Starknet +storage + +## Implementation + +We need to be able to store in the local state the current information: + +- The existing modified accounts content, including balance, nonce, code and + storage updates, +- The emitted events, +- The newly created accounts, +- The deleted accounts through `selfdestruct` +- The pending transfers of native tokens. + +At the end of an execution context, we can merge the contextual state into the +transactional state, and at the end of a transaction, we can apply the +transactional state updates to the blockchain storage, emit pending events, and +perform actual token transfers. + +## Implementation design + +The state is implemented as a struct composed by `StateChangeLog` - for objects +that need to be overridden in the State - and `SimpleLog` data structures, for +objects that only need to be appended to a list. They are implemented as +follows: + +```rust +struct State { + /// Accounts states - without storage and balances, which are handled separately. + accounts: StateChangeLog, + /// Account storage states. `EthAddress` indicates the target contract, + /// `u256` indicates the storage key. + /// `u256` indicates the value stored. + /// We have to store the target contract, as we can't derive it from the + /// hashed address only when finalizing. + accounts_storage: StateChangeLog<(EthAddress, u256, u256)>, + /// Account states + /// Pending emitted events + events: SimpleLog, + /// Account balances updates. This is only internal accounting and stored + /// balances are updated when committing transfers. + balances: StateChangeLog, + /// Pending transfers + transfers: SimpleLog, +} +``` + +The State struct + +A `StateChangeLog` is a data structure that tracks the changes made to a +specific object both at the context-level and at the transaction-level using the +mechanism specified above. The `SimpleLog` is a simplified version of this +concept as an append-only data structure. + +```rust +/// The `StateChangeLog` tracks the changes applied to storage during the execution of a transaction. +/// Upon exiting an execution context, contextual changes must be finalized into transactional changes. +/// Upon exiting the transaction, transactional changes must be finalized into storage updates. +/// +/// # Type Parameters +/// +/// * `T` - The type of values stored in the log. +/// +/// # Fields +/// +/// * `contextual_changes` - A `Felt252Dict` of contextual changes. Tracks the changes applied inside a single execution context. +/// * `contextual_keyset` - An `Array` of contextual keys. +/// * `transactional_changes` - A `Felt252Dict` of transactional changes. Tracks +/// the changes applied in the entire transaction. +/// * `transactional_keyset` - An `Array` of transactional keys. +struct StateChangeLog { + contextual_changes: Felt252Dict>, + contextual_keyset: Array, + transactional_changes: Felt252Dict>, + transactional_keyset: Array +} +``` + +The StateChangeLog generic struct + +```rust + +/// `SimpleLog` is a straightforward logging mechanism. +/// +/// This structure is designed to manage both contextual and transactional logs of a generic type `T`. +/// +/// # Fields +/// +/// - `contextual_logs`: Contains logs that are context-specific. +/// - `transactional_logs`: Contains logs that are transaction-wide. +#[derive(Drop)] +struct SimpleLog { + contextual_logs: Array, + transactional_logs: Array, +} +``` + +The SimpleLog generic struct + +Storage changes are modeled using a dictionary where the key is the storage +address to update, and the value is the updated value. However, we can't create +nested dictionaries in Cairo - so we have to separate the accounts storage +updates from the account updates themselves. diff --git a/cairo/kakarot-ssj/docs/general/machine.md b/cairo/kakarot-ssj/docs/general/machine.md new file mode 100644 index 000000000..efc8f6d07 --- /dev/null +++ b/cairo/kakarot-ssj/docs/general/machine.md @@ -0,0 +1,242 @@ +# Kakarot's EVM - Internal Design + +The EVM is a stack-based computer responsible for the execution of EVM bytecode. +It has two context-bound data structures: the stack and the memory. The stack is +a 256bit-words based data structure used to store and retrieve intermediate +values during the execution of opcodes. The memory is a byte-addressable data +structure organized into 32-byte words used a volatile space to store data +during execution. Both the stack and the memory are initialized empty at the +start of a call context, and destroyed when a call context ends. + +The initial design of Kakarot's EVM had a single struct to model the execution +context, which contained the stack, the memory, and the execution state. Each +local execution context optionally contained parent and child execution +contexts, which were used to model the execution of sub-calls. However, this +design was not possible to implement in Cairo, as Cairo does not support the use +of `Nullable` types containing dictionaries. Since the `ExecutionContext` struct +contains such `Nullable` types, we had to change the design of the EVM to use a +machine with a single stack and memory, which are our dict-based data +structures. + +## The Kakarot Machine design + +To overcome the problem stated above, we have come up with the following design: + +- There is only one instance of the Memory and the Stack, which is shared + between the different execution contexts. +- Each execution context has its own identifier `id`, which uniquely identifies + it. +- Each execution context has a `parent_ctx` field, which value is either a + pointer to its parent execution context or `null`. +- Each execution context has a `return_data` field, whose value is either + nothing or the return data from the child context. This is meant to enable + opcodes `RETURNDATASIZE` and `RETURNDATACOPY`. These two opcodes are the only + ones enabling a current context to access its child context's return data. +- The execution context tree is a directed acyclic graph, where each execution + context has at most one parent. +- A specific execution context is accessible by traversing the execution context + tree, starting from the root execution context, and following the execution + context tree until the desired execution context is reached. The machine also + stores a pointer to the current execution context. +- The execution context tree is initialized with a single root execution + context, which has no parent and no child. It has an `id` field equal to 0. + +The following diagram describes the model of the Kakarot machine. + +```mermaid +classDiagram + class Machine{ + current_ctx: Box~ExecutionContext~, + ctx_count: usize, + stack: Stack, + memory: Memory, + state: State, + } + + class Memory{ + active_segment: usize, + items: Felt252Dict~u128~, + bytes_len: Felt252Dict~usize~, + } + + class Stack{ + +active_segment: usize, + +items: Felt252Dict~Nullable~u256~~, + +len: Felt252Dict~usize~ + } + + class ExecutionContext{ + ctx_type: ExecutionContextType, + address: Address, + program_counter: u32, + status: Status, + call_ctx: Box~CallContext~, + return_data: Span~u8~, + parent_ctx: Nullable~ExecutionContext~, + gas_used: u128, + } + + class ExecutionContextType { + <> + Root: IsCreate, + Call: usize, + Create: usize + } + + class CallContext{ + caller: EthAddress, + value: u256, + bytecode: Span~u8~, + calldata: Span~u8~, + gas_price: u128, + gas_limit: u128, + read_only: bool, + ret_offset: usize, + ret_size: usize, + } + + class State{ + accounts: StateChangeLog~Account~, + accounts_storage: StateChangeLog~EthAddress_u256_u256~, + events: SimpleLog~Event~, + transfers: SimpleLog~Transfer~, + } + + class StateChangeLog~T~ { + contextual_changes: Felt252Dict~Nullable~T~~, + contextual_keyset: Array~felt252~, + transactional_changes: Felt252Dict~Nullable~T~~, + transactional_keyset: Array~felt252~ + } + + class SimpleLog~T~ { + contextual_logs: Array~T~, + transactional_logs: Array~T~, + } + + class Status{ + <> + Active, + Stopped, + Reverted + } + + + Machine *-- Memory + Machine *-- Stack + Machine *-- ExecutionContext + Machine *-- State + ExecutionContext *-- ExecutionContext + ExecutionContext *-- ExecutionContextType + ExecutionContext *-- CallContext + ExecutionContext *-- Status + State *-- StateChangeLog + State *-- SimpleLog +``` + +Kakarot internal architecture model + +### The Stack + +Instead of having one Stack per execution context, we have a single Stack shared +between all execution contexts. Because our Stack is a dict-based data +structure, we can actually simulate multiple stacks by using different keys for +each execution context. The `active_segment` field of the Stack is used to keep +track of the current active execution context. The `len` field is a dictionary +field is a dictionary mapping execution context identifiers to the length of +their corresponding stacks. The `items` field is a dictionary mapping indexes to +values. + +The EVM imposes a limit of a maximum of 1024 items on the stack. At any given +time, the stack relative to an execution context contains at most 1024 items. +This means that if we consider items to be stored at sequential indexes, the +stack relative to an execution context is a contiguous segment of the global +stack of maximum size `1024`. When pushing an item to the stack, we will compute +an index which corresponds to the index in the dict the item will be stored at. +The internal index is computed as follows: + +$$index = len(Stack_i) + i \cdot 1024$$ + +where $i$ is the id of the active execution context. + +If we want to push an item to the stack of the root context, the internal index +will be $index = len(Stack_0) + 0 \cdot 1024 = len(Stack_0)$. + +The process is analogous for popping an item from the stack. + +### The Memory + +The Memory is modeled in a similar way to the Stack. The difference is that the +memory doesn't have a limit on the number of items it can store. Instead, the +cost of expanding the size of the memory grows quadratically relative to its +size. Given that an Ethereum block has a gas limit, we can assume that the +maximum size of the memory is bounded by the gas limit of a block, which is 30M +gas. + +The expansion cost of the memory is defined as follows in the Ethereum Yellow +Paper: + +$$C_{mem}(a) \equiv G_{memory} Β· a + [\frac{a^2}{512}]$$ + +where $G_{memory} = 3$ and $a$ is the number of 32-byte words allocated. + +Following this formula, the gas costs required to have a memory containing +125000 words is above the 30M gas limit. We will use this heuristic to bound the +maximum size of the memory to the closest power of two to 125000: $2^17$. +Therefore, we will bound the maximum size of the memory to 131072 256-bits +words. + +The internal index at which an item will be inserted in the memory, given a +specific offset, is computed as: + +$$index = offset + i \cdot 131072$$ + +where $i$ is the id of the active execution context. + +If we want to store an item at offset 10 of the memory relative to the execution +context of id 1, the internal index will be +$index = 10 + 1 \cdot 131072 = 131082$. + +## Execution flow + +The following diagram describe the flow of the execution context when executing +the `run` function given an instance of the `Machine` struct instantiated with +the bytecode to execute and the appropriate execution context. + +The run function is responsible for executing EVM bytecode. The flow of +execution involves decoding and executing the current opcode, handling the +execution, and continue executing the next opcode if the execution of the +previous one succeeded. If the execution of an opcode fails, the execution +context reverts, the changes made in this context are dropped, and the state of +the blockchain is not updated. + +```mermaid +flowchart TD +AA["START"] --> A +A["run()"] --> B[Decode and Execute Opcode] +B --> |pc+=1| C{Result OK?} +C -->|Yes| D{Execution stopped?} +D -->|No| A +D -->|Yes| F{Reverted?} +C -->|No| RA +F --> |No| FA +F -->|Yes| RA[Discard account updates] + +subgraph Discard context changes +RA --> RB["Discard storage updates"] +RB --> RC["Discard event log"] +RC --> RD["Discard transfers log"] +end + +RD --> FA[finalize context] +``` + +Execution flow of EVM bytecode + +## Conclusion + +With its shared stack and memory accessed via calculated internal indexes +relative to the current execution context, this EVM design remains compatible +with the original EVM design, easily refactorable if we implement all +interactions with the machine through type methods, and compatible with Cairo's +limitations. diff --git a/cairo/kakarot-ssj/docs/general/volition.png b/cairo/kakarot-ssj/docs/general/volition.png new file mode 100644 index 000000000..bc76e843b Binary files /dev/null and b/cairo/kakarot-ssj/docs/general/volition.png differ diff --git a/cairo/kakarot-ssj/docs/img/kakarot_github_banner.png b/cairo/kakarot-ssj/docs/img/kakarot_github_banner.png new file mode 100644 index 000000000..0a0ba283e Binary files /dev/null and b/cairo/kakarot-ssj/docs/img/kakarot_github_banner.png differ diff --git a/cairo/kakarot-ssj/package-lock.json b/cairo/kakarot-ssj/package-lock.json new file mode 100644 index 000000000..a5570a15c --- /dev/null +++ b/cairo/kakarot-ssj/package-lock.json @@ -0,0 +1,1252 @@ +{ + "name": "kakarot-ssj", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kakarot-ssj", + "dependencies": { + "@ethereumjs/tx": "^5.0.0", + "@types/inquirer": "^9.0.7", + "dotenv": "^16.3.1", + "ethers": "^6.8.1", + "inquirer": "^9.2.12", + "rlp": "^3.0.0", + "starknet": "^5.19.5", + "viem": "^2.21.17" + }, + "devDependencies": { + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT" + }, + "node_modules/@ethereumjs/common": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-4.1.0.tgz", + "integrity": "sha512-XWdQvUjlQHVwh4uGEPFKHpsic69GOsMXEhlHrggS5ju/+2zAmmlz6B25TkCCymeElC9DUp13tH5Tc25Iuvtlcg==", + "dependencies": { + "@ethereumjs/util": "^9.0.1", + "crc": "^4.3.2" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.1.tgz", + "integrity": "sha512-Ab/Hfzz+T9Zl+65Nkg+9xAmwKPLicsnQ4NW49pgvJp9ovefuic95cgOS9CbPc9izIEgsqm1UitV0uNveCvud9w==", + "bin": { + "rlp": "bin/rlp.cjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-5.1.0.tgz", + "integrity": "sha512-VUhw2+4yXArJZRWhPjmZFrN4WUjUo0qUZUszVpW2KzsGlqCFf67kwJcH9Rca5eS0CRHjr2qHJLpvYOjNuaXVdA==", + "dependencies": { + "@ethereumjs/common": "^4.1.0", + "@ethereumjs/rlp": "^5.0.1", + "@ethereumjs/util": "^9.0.1", + "ethereum-cryptography": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "c-kzg": "^2.1.2" + }, + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } + } + }, + "node_modules/@ethereumjs/util": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-9.0.1.tgz", + "integrity": "sha512-NdFFEzCc3H1sYkNnnySwLg6owdQMhjUc2jfuDyx8Xv162WSluCnnSKouKOSG3njGNEyy2I9NmF8zTRDwuqpZWA==", + "dependencies": { + "@ethereumjs/rlp": "^5.0.1", + "ethereum-cryptography": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "c-kzg": "^2.1.2" + }, + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz", + "integrity": "sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@scure/starknet/-/starknet-0.3.0.tgz", + "integrity": "sha512-Ma66yZlwa5z00qI5alSxdWtIpky5LBhy22acVFdoC5kwwbd9uDyMWEYzWHdNyKmQg9t5Y2UOXzINMeb3yez+Gw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/curves": "~1.2.0", + "@noble/hashes": "~1.3.2" + } + }, + "node_modules/@scure/starknet/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/inquirer": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.7.tgz", + "integrity": "sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==", + "license": "MIT", + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, + "node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abitype": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.5.tgz", + "integrity": "sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bun-types": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.14.tgz", + "integrity": "sha512-hLVfM2fk8xSJeobfuPGilfvxM5gLtEy1bn7RJhrtw3u4OaC0kieXKYFFTVHU8jZ3hj8YyPLIFClIUodkFSrMBQ==", + "dev": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/crc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/crc/-/crc-4.3.2.tgz", + "integrity": "sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "buffer": ">=6.0.3" + }, + "peerDependenciesMeta": { + "buffer": { + "optional": true + } + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ethereum-cryptography": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", + "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", + "dependencies": { + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + } + }, + "node_modules/ethers": { + "version": "6.13.3", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.3.tgz", + "integrity": "sha512-/DzbZOLVtoO4fKvvQwpEucHAQgIwBGWuRvBdwE/lMXgXvvHHTSkn7XqAQ2b+gjJzZDJjWA9OD05bVceVOsBHbg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "9.3.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.7.tgz", + "integrity": "sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==", + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/isows": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.4.tgz", + "integrity": "sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lossless-json": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-2.0.11.tgz", + "integrity": "sha512-BP0vn+NGYvzDielvBZaFain/wgeJ1hTvURCqtKvhr1SCPePdaaTanmmcplrHfEJSJOUql7hk4FHwToNJjWRY3g==" + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rlp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-3.0.0.tgz", + "integrity": "sha512-PD6U2PGk6Vq2spfgiWZdomLvRGDreBLxi5jv5M8EpRo3pU6VEm31KO+HFxE18Q3vgqfDrQ9pZA3FP95rkijNKw==", + "bin": { + "rlp": "bin/rlp" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/starknet": { + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/starknet/-/starknet-5.24.3.tgz", + "integrity": "sha512-v0TuaNc9iNtHdbIRzX372jfQH1vgx2rwBHQDMqK4DqjJbwFEE5dog8Go6rGiZVW750NqRSWrZ7ahqyRNc3bscg==", + "dependencies": { + "@noble/curves": "~1.2.0", + "@scure/base": "^1.1.3", + "@scure/starknet": "~0.3.0", + "isomorphic-fetch": "^3.0.0", + "lossless-json": "^2.0.8", + "pako": "^2.0.4", + "url-join": "^4.0.1" + } + }, + "node_modules/starknet/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/starknet/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.21.17", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.21.17.tgz", + "integrity": "sha512-YtqH0JZxmxQ4KBzXFwIe2EMFydlb8oOcwYnXgnNNOTy5nryEVkEO3Dbf7/VzFOIVsatr778i+QbUtSO60bKGkw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.0", + "@noble/curves": "1.4.0", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.4.0", + "abitype": "1.0.5", + "isows": "1.0.4", + "webauthn-p256": "0.0.5", + "ws": "8.17.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", + "license": "MIT" + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/bip39": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.4.0.tgz", + "integrity": "sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webauthn-p256": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.5.tgz", + "integrity": "sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + } + }, + "node_modules/webauthn-p256/node_modules/@noble/curves": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/webauthn-p256/node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/cairo/kakarot-ssj/package.json b/cairo/kakarot-ssj/package.json new file mode 100644 index 000000000..25687aee3 --- /dev/null +++ b/cairo/kakarot-ssj/package.json @@ -0,0 +1,24 @@ +{ + "name": "kakarot-ssj", + "type": "module", + "devDependencies": { + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@ethereumjs/tx": "^5.0.0", + "@types/inquirer": "^9.0.7", + "dotenv": "^16.3.1", + "ethers": "^6.8.1", + "inquirer": "^9.2.12", + "rlp": "^3.0.0", + "starknet": "^5.19.5", + "viem": "^2.21.17" + }, + "scripts": { + "compute_starknet_address": "bun run scripts/compute_starknet_address.ts", + "compute_create_address": "bun run scripts/compute_create_address.ts" + } +} diff --git a/cairo/kakarot-ssj/scripts/compare_snapshot.py b/cairo/kakarot-ssj/scripts/compare_snapshot.py new file mode 100644 index 000000000..fa725cb71 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/compare_snapshot.py @@ -0,0 +1,159 @@ +import json +import logging +import os +import re +import subprocess +import tempfile +import zipfile +from pathlib import Path + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def get_github_token_from_env(file_path=".env"): + """Read the .env file and extract the GITHUB_TOKEN value.""" + try: + with open(file_path, "r") as file: + for line in file: + if line.startswith("#"): + continue + key, value = line.strip().split("=", 1) + if key == "GITHUB_TOKEN": + return value if value != "" else None + except FileNotFoundError: + return None + except ValueError: + logger.error( + f"Error: Invalid format in {file_path}. Expected 'KEY=VALUE' format." + ) + return None + + +def get_previous_snapshot(): + REPO = "kkrt-labs/kakarot-ssj" # Replace with your GitHub username and repo name + GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", get_github_token_from_env()) + if GITHUB_TOKEN is None: + raise ValueError( + "GITHUB_TOKEN doesn't exist in current shell nor is defined .env" + ) + + # Fetch the list of workflow runs + cmd = f"curl -s -H 'Authorization: token {GITHUB_TOKEN}' -H 'Accept: application/vnd.github.v3+json' 'https://api.github.com/repos/{REPO}/actions/runs?branch=main&per_page=100'" + result = subprocess.check_output(cmd, shell=True) + runs = json.loads(result) + + # Find the latest successful run + latest_successful_run = next( + ( + run + for run in runs["workflow_runs"] + if run["conclusion"] == "success" + and run["name"] == "Generate and Upload Gas Snapshot" + ), + None, + ) + + if latest_successful_run is None: + return + + # Fetch the artifacts for that run + run_id = latest_successful_run["id"] + cmd = f"curl -s -H 'Authorization: token {GITHUB_TOKEN}' -H 'Accept: application/vnd.github.v3+json' 'https://api.github.com/repos/{REPO}/actions/runs/{run_id}/artifacts'" + result = subprocess.check_output(cmd, shell=True) + artifacts = json.loads(result) + + # Find the gas_snapshot.json artifact + snapshot_artifact = next( + ( + artifact + for artifact in artifacts["artifacts"] + if artifact["name"] == "gas-snapshot" + ), + None, + ) + + if snapshot_artifact is None: + return + + # Download the gas_snapshots.json archive + temp_dir = Path(tempfile.mkdtemp()) + + archive_name = temp_dir / "gas_snapshot.zip" + cmd = f"curl -s -L -o {archive_name} -H 'Authorization: token {GITHUB_TOKEN}' -H 'Accept: application/vnd.github.v3+json' '{snapshot_artifact['archive_download_url']}'" + subprocess.check_call(cmd, shell=True) + with zipfile.ZipFile(archive_name, "r") as archive: + archive.extractall(temp_dir) + + return json.loads(archive_name.with_suffix(".json").read_text()) + + +def get_current_gas_snapshot(): + """Execute command and return current gas snapshots.""" + output = subprocess.check_output("scarb test", shell=True).decode("utf-8") + pattern = r"test ([\w\:]+).*gas usage est\.\: (\d+)" + matches = re.findall(pattern, output) + matches.sort() + return {match[0]: int(match[1]) for match in matches} + + +def compare_snapshots(current, previous): + """Compare current and previous snapshots and return differences.""" + worsened = [] + improvements = [] + common_keys = list(set(current.keys()) & set(previous.keys())) + common_keys.sort() + max_key_len = max(len(key) for key in common_keys) + for key in common_keys: + prev = previous[key] + cur = current[key] + log = ( + f"|{key:<{max_key_len + 5}} | {prev:>10} | {cur:>10} | {cur / prev:>6.2%}|" + ) + if prev < cur: + worsened.append(log) + elif prev > cur: + improvements.append(log) + + return improvements, worsened + + +def total_gas_used(current, previous): + """Return the total gas used in the current and previous snapshot, not taking into account added tests.""" + common_keys = set(current.keys()) & set(previous.keys()) + + cur_gas = sum(current[key] for key in common_keys) + prev_gas = sum(previous[key] for key in common_keys) + + return cur_gas, prev_gas + + +def main(): + previous_snapshot = get_previous_snapshot() + if previous_snapshot is None: + logger.error("Error: Failed to load previous snapshot.") + return + + current_snapshots = get_current_gas_snapshot() + improvements, worsened = compare_snapshots(current_snapshots, previous_snapshot) + cur_gas, prev_gas = total_gas_used(current_snapshots, previous_snapshot) + header = [ + "| Test | Prev | Cur | Ratio |", + "| ---- | ---- | --- | ----- |", + ] + if improvements: + logger.info("\n".join(["****BETTER****"] + header + improvements)) + if worsened: + logger.info("\n".join(["****WORST****"] + header + worsened)) + + logger.info( + f"\nTotal gas change: {prev_gas} -> {cur_gas} ({cur_gas / prev_gas:.2%})" + ) + if worsened: + logger.error("Gas usage increased") + else: + logger.info("Gas change βœ…") + + +if __name__ == "__main__": + main() diff --git a/cairo/kakarot-ssj/scripts/compute_create_address.ts b/cairo/kakarot-ssj/scripts/compute_create_address.ts new file mode 100644 index 000000000..e912c34fd --- /dev/null +++ b/cairo/kakarot-ssj/scripts/compute_create_address.ts @@ -0,0 +1,60 @@ +import { Address, Hex, getContractAddress } from "viem"; +import { createPromptModule } from "inquirer"; + +const prompt = createPromptModule(); + +prompt([ + { + type: "list", + name: "opcode", + message: "Choose an opcode:", + choices: ["CREATE", "CREATE2"], + }, + { + type: "input", + name: "from", + message: "Enter from address:", + default: "0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266", + }, + { + type: "input", + name: "nonce", + message: "Enter nonce:", + default: "420", + when: (answers) => answers.opcode === "CREATE", + filter: (value) => BigInt(value), + }, + { + type: "input", + name: "bytecode", + message: "Enter bytecode", + default: + "0x608060405234801561000f575f80fd5b506004361061004a575f3560e01c806306661abd1461004e578063371303c01461006c5780636d4ce63c14610076578063b3bcfa8214610094575b5f80fd5b61005661009e565b60405161006391906100f7565b60405180910390f35b6100746100a3565b005b61007e6100bd565b60405161008b91906100f7565b60405180910390f35b61009c6100c5565b005b5f5481565b60015f808282546100b4919061013d565b92505081905550565b5f8054905090565b60015f808282546100d69190610170565b92505081905550565b5f819050919050565b6100f1816100df565b82525050565b5f60208201905061010a5f8301846100e8565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610147826100df565b9150610152836100df565b925082820190508082111561016a57610169610110565b5b92915050565b5f61017a826100df565b9150610185836100df565b925082820390508181111561019d5761019c610110565b5b9291505056fea26469706673582212207e792fcff28a4bf0bad8675c5bc2288b07835aebaa90b8dc5e0df19183fb72cf64736f6c63430008160033", + when: (answers) => answers.opcode === "CREATE2", + }, + { + type: "input", + name: "salt", + message: "Enter salt or press Enter for default [0xbeef]:", + default: "0xbeef", + when: (answers) => answers.opcode === "CREATE2", + }, +]).then((answers) => { + let address: Address; + if (answers.opcode === "CREATE") { + address = getContractAddress({ + opcode: "CREATE", + from: answers.from as Address, + nonce: answers.nonce, + }); + } else if (answers.opcode === "CREATE2") { + address = getContractAddress({ + opcode: "CREATE2", + from: answers.from as Address, + bytecode: answers.bytecode as Hex, + salt: answers.salt as Hex, + }); + } + + console.log(`Generated Address: ${address!}`); +}); diff --git a/cairo/kakarot-ssj/scripts/compute_rlp_encoding.ts b/cairo/kakarot-ssj/scripts/compute_rlp_encoding.ts new file mode 100644 index 000000000..a556f0b33 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/compute_rlp_encoding.ts @@ -0,0 +1,160 @@ +// This js script helps in creating unsigned and signed RLP data for tests + +import { ethers, toBeArray } from "ethers"; +import dotevn from "dotenv"; +import readline from "readline"; +import { readFileSync } from "fs"; + +dotevn.config(); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const question = (query: string): Promise => { + return new Promise((resolve, reject) => { + rl.question(query, (answer) => { + resolve(answer); + }); + }); +}; + +const main = async () => { + const { Transaction, Wallet } = ethers; + const { decodeRlp, getBytes } = ethers; + + if (!process.env.PRIVATE_KEY_RLP_SCRIPT) { + console.log( + "missing private key in environment, please provide PRIVATE_KEY_RLP_SCRIPT environment variable", + ); + process.exit(1); + } + + const wallet = new Wallet(process.env.PRIVATE_KEY_RLP_SCRIPT); + console.log("address of the wallet is", wallet.address); + + let tx_type = parseInt( + await question( + "enter transaction, 0: legacy, 1: 2930, 2:1559, 3: inc_counter, 4: y_parity_false eip1559: ", + ), + ); + + // for type 0 and type 1 + let tx; + + switch (tx_type) { + case 0: + tx = JSON.parse( + readFileSync("./scripts/data/input_legacy_tx.json", "utf-8"), + ); + break; + case 1: + tx = JSON.parse( + readFileSync("./scripts/data/input_access_list_tx.json", "utf-8"), + ); + break; + case 2: + tx = JSON.parse( + readFileSync("./scripts/data/input_fee_tx.json", "utf-8"), + ); + break; + case 3: + tx_type = 1; + tx = JSON.parse( + readFileSync( + "./scripts/data/input_eip_2930_counter_inc_tx.json", + "utf-8", + ), + ); + break; + case 4: + tx_type = 2; + tx = JSON.parse( + readFileSync( + "./scripts/data/input_eip1559_y_parity_false.json", + "utf-8", + ), + ); + break; + default: + throw new Error( + `transaction type ${tx_type} isn't a valid transaction type`, + ); + } + + const transaction = Transaction.from(tx); + transaction.type = tx_type; + + let signed_tx = await wallet.signTransaction(transaction); + + console.log("unsigned serialized tx ----->", transaction.unsignedSerialized); + console.log("unsigned transaction hash", transaction.hash); + + // const bytes = getBytes(signedTX); + const bytes = getBytes(transaction.unsignedSerialized); + + console.log("unsigned RLP encoded bytes for the transaction: "); + + // this prints unsigned RLP encoded bytes of the transaction + bytes.forEach((v) => { + console.log(v, ","); + }); + console.log("\n"); + + let bytes2 = Uint8Array.from(transaction.type == 0 ? bytes : bytes.slice(1)); + + let decodedRlp = decodeRlp(bytes2); + console.log("decoded RLP is for unsigned transaction ....\n", decodedRlp); + + let bytes3 = getBytes(signed_tx); + + console.log("signed RLP encoded bytes for the transaction: "); + + // this prints unsigned RLP encoded bytes of the transaction + bytes3.forEach((v) => { + console.log(v, ","); + }); + console.log("\n"); + + bytes3 = Uint8Array.from(transaction.type == 0 ? bytes3 : bytes3.slice(1)); + decodedRlp = decodeRlp(bytes3); + console.log("signed decoded RLP for signed transaction ....\n", decodedRlp); + + const hash = ethers.keccak256(bytes); + console.log("the hash over which the signature was made:", hash); + + console.log("signature details: "); + const v = decodedRlp[decodedRlp.length - 3]; + const r = decodedRlp[decodedRlp.length - 2]; + const s = decodedRlp[decodedRlp.length - 1]; + + const y_parity = + tx_type == 0 + ? get_y_parity(BigInt(v), BigInt(tx.chainId)) + : parseInt(v, 16) == 1; + console.log("r: ", r); + console.log("s: ", s); + if (tx_type == 0) { + console.log("v: ", v); + } + console.log("y parity: ", y_parity); + + process.exit(0); +}; + +const get_y_parity = (v: bigint, chain_id: bigint): boolean => { + let y_parity = v - (chain_id * BigInt(2) + BigInt(35)); + if (y_parity == BigInt(0) || y_parity == BigInt(1)) { + return y_parity == BigInt(1); + } + + y_parity = v - (chain_id * BigInt(2) + BigInt(36)); + if (y_parity == BigInt(0) || y_parity == BigInt(1)) { + return y_parity == BigInt(1); + } + + throw new Error("invalid v value"); +}; + +main(); diff --git a/cairo/kakarot-ssj/scripts/compute_starknet_address.ts b/cairo/kakarot-ssj/scripts/compute_starknet_address.ts new file mode 100644 index 000000000..5e3cafd52 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/compute_starknet_address.ts @@ -0,0 +1,48 @@ +import { hash } from "starknet"; +import inquirer from "inquirer"; + +inquirer + .prompt([ + { + type: "input", + name: "classHashInput", + message: "Enter the class hash:", + validate: (input) => { + if (input.trim() === "") { + return "Class hash is required."; + } + return true; + }, + }, + { + type: "input", + name: "saltInput", + message: "Enter the salt", + default: "0x65766d5f61646472657373", + }, + { + type: "input", + name: "deployerInput", + message: "Enter the deployer address", + default: + "0x7753aaa1814b9f978fd93b66453ae87419b66d764fbf9313847edeb0283ef63", + }, + ]) + .then((answers) => { + const classHash = BigInt(answers.classHashInput); + const salt = BigInt(answers.saltInput); + const deployerAddress = BigInt(answers.deployerInput); + + const CONSTRUCTOR_CALLDATA = [deployerAddress, salt]; + + function compute_starknet_address() { + return hash.calculateContractAddressFromHash( + salt, + classHash, + CONSTRUCTOR_CALLDATA, + deployerAddress, + ); + } + + console.log("Pre-computed Starknet Address: " + compute_starknet_address()); + }); diff --git a/cairo/kakarot-ssj/scripts/data/input_access_list_tx.json b/cairo/kakarot-ssj/scripts/data/input_access_list_tx.json new file mode 100644 index 000000000..3c5b2a88e --- /dev/null +++ b/cairo/kakarot-ssj/scripts/data/input_access_list_tx.json @@ -0,0 +1,18 @@ +{ + "nonce": "0x0", + "gasPrice": "0x3b9aca00", + "gasLimit": "0x1e8480", + "to": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "value": "0x016345785d8a0000", + "data": "0xabcdef", + "accessList": [ + [ + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + [ + "0xde9fbe35790b85c23f42b7430c78f122636750cc217a534c80a9a0520969fa65", + "0xd5362e94136f76bfc8dad0b510b94561af7a387f1a9d0d45e777c11962e5bd94" + ] + ] + ], + "chainId": "0x4b4b5254" +} diff --git a/cairo/kakarot-ssj/scripts/data/input_eip1559_y_parity_false.json b/cairo/kakarot-ssj/scripts/data/input_eip1559_y_parity_false.json new file mode 100644 index 000000000..16ac42ade --- /dev/null +++ b/cairo/kakarot-ssj/scripts/data/input_eip1559_y_parity_false.json @@ -0,0 +1,11 @@ +{ + "nonce": "0x1", + "maxPriorityFeePerGas": "0x64", + "maxFeePerGas": "0x3e8", + "gasLimit": "0x64c9", + "to": "0xcccccccccccccccccccccccccccccccccccccccc", + "value": "0x0", + "data": "0x00", + "accessList": [], + "chainId": "0x4b4b5254" +} diff --git a/cairo/kakarot-ssj/scripts/data/input_eip_2930_counter_inc_tx.json b/cairo/kakarot-ssj/scripts/data/input_eip_2930_counter_inc_tx.json new file mode 100644 index 000000000..9792f0baa --- /dev/null +++ b/cairo/kakarot-ssj/scripts/data/input_eip_2930_counter_inc_tx.json @@ -0,0 +1,10 @@ +{ + "chainId": "0x4b4b5254", + "nonce": "0x0", + "gasPrice": "0x3b9aca00", + "gasLimit": "0x1e8480", + "to": "0x0000006f746865725f65766d5f61646472657373", + "value": "0x0", + "data": "0x371303c0", + "accessList": [] +} diff --git a/cairo/kakarot-ssj/scripts/data/input_fee_tx.json b/cairo/kakarot-ssj/scripts/data/input_fee_tx.json new file mode 100644 index 000000000..3ad77596e --- /dev/null +++ b/cairo/kakarot-ssj/scripts/data/input_fee_tx.json @@ -0,0 +1,19 @@ +{ + "nonce": "0x0", + "maxPriorityFeePerGas": "0x0", + "maxFeePerGas": "0x3b9aca00", + "gasLimit": "0x1e8480", + "to": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "value": "0x016345785d8a0000", + "data": "0xabcdef", + "accessList": [ + [ + "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + [ + "0xde9fbe35790b85c23f42b7430c78f122636750cc217a534c80a9a0520969fa65", + "0xd5362e94136f76bfc8dad0b510b94561af7a387f1a9d0d45e777c11962e5bd94" + ] + ] + ], + "chainId": "0x4b4b5254" +} diff --git a/cairo/kakarot-ssj/scripts/data/input_legacy_deploy_tx.json b/cairo/kakarot-ssj/scripts/data/input_legacy_deploy_tx.json new file mode 100644 index 000000000..48dc3c338 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/data/input_legacy_deploy_tx.json @@ -0,0 +1,9 @@ +{ + "nonce": "0x0", + "gasPrice": "0x0a", + "gasLimit": "0x061a80", + "to": null, + "value": "0x0186a0", + "data": "0x600160010a5060006000f3", + "chainId": "0x4b4b5254" +} diff --git a/cairo/kakarot-ssj/scripts/data/input_legacy_tx.json b/cairo/kakarot-ssj/scripts/data/input_legacy_tx.json new file mode 100644 index 000000000..65349db27 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/data/input_legacy_tx.json @@ -0,0 +1,9 @@ +{ + "nonce": "0x0", + "gasPrice": "0x3b9aca00", + "gasLimit": "0x1e8480", + "to": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "value": "0x016345785d8a0000", + "data": "0xabcdef", + "chainId": "0x4b4b5254" +} diff --git a/cairo/kakarot-ssj/scripts/filter_tests.py b/cairo/kakarot-ssj/scripts/filter_tests.py new file mode 100644 index 000000000..7d514b79a --- /dev/null +++ b/cairo/kakarot-ssj/scripts/filter_tests.py @@ -0,0 +1,50 @@ +import os +import re +import sys + + +def filter_tests(directory, filter_string): + for root, _, files in os.walk(directory): + for file in files: + if file.endswith(".cairo"): + file_path = os.path.join(root, file) + filter_file(file_path, filter_string) + + print(f"Filtered tests for {filter_string}") + + +def filter_file(file_path, filter_string): + with open(file_path, "r") as f: + content = f.read() + + # Regular expression to match test functions, including nested braces + test_pattern = re.compile( + r"#\[test\]\s*(?:#\[available_gas\([^\)]+\)\]\s*)?fn\s+(\w+)\s*\([^)]*\)\s*(\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})", + re.DOTALL, + ) + + def replace_func(match): + full_match = match.group(0) + func_name = match.group(1) + if filter_string.lower() in func_name.lower(): + return full_match + else: + return "" + + new_content = test_pattern.sub(replace_func, content) + + if new_content != content: + with open(file_path, "w") as f: + f.write(new_content) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python filter_tests.py ") + sys.exit(1) + + filter_string = sys.argv[1] + crates_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "crates" + ) + filter_tests(crates_dir, filter_string) diff --git a/cairo/kakarot-ssj/scripts/find_selectory.py b/cairo/kakarot-ssj/scripts/find_selectory.py new file mode 100644 index 000000000..e126ad432 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/find_selectory.py @@ -0,0 +1,30 @@ +import os +import re + +from starkware.starknet.public.abi import get_selector_from_name + + +def find_cairo_functions(directory): + return [ + match + for root, _, files in os.walk(directory) + for file in files + if file.endswith(".cairo") + for match in re.findall(r"fn\s+(\w+)\(", open(os.path.join(root, file)).read()) + ] + + +def map_selectors(functions): + return {get_selector_from_name(function): function for function in functions} + + +def get_function_from_selector(selectors): + selector = int(input("Enter the hexadecimal selector: "), 16) + print(f"Corresponding function: {selectors.get(selector, 'Not found')}") + + +if __name__ == "__main__": + directory = "." + functions = find_cairo_functions(directory) + selectors = map_selectors(functions) + get_function_from_selector(selectors) diff --git a/cairo/kakarot-ssj/scripts/gas_debug_call.py b/cairo/kakarot-ssj/scripts/gas_debug_call.py new file mode 100644 index 000000000..f9a5a49a7 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/gas_debug_call.py @@ -0,0 +1,38 @@ +import re + + +def process_logs(logs): + gas_consumption = {} + previous_gas = {} + + pattern = re.compile( + r"Address (\d+), opcode (\w+), pc (\d+), gas left in call (\d+)" + ) + + for line in logs.split("\n"): + match = pattern.search(line) + if match: + address, opcode, pc, gas_left = match.groups() + address = int(address) + pc = int(pc) + gas_left = int(gas_left) + + if address not in gas_consumption: + gas_consumption[address] = 0 + previous_gas[address] = gas_left + else: + gas_used = previous_gas[address] - gas_left + gas_consumption[address] += gas_used + previous_gas[address] = gas_left + + print( + f"{hex(address)} - {pc} - {opcode} --> total gas used: {gas_consumption[address]}" + ) + + +# Example usage +logs = """ +Address 1169201309864722334562947866173026415724746034380, opcode 96, pc 1, gas left in call 79978644 +""" + +process_logs(logs) diff --git a/cairo/kakarot-ssj/scripts/gen_snapshot.py b/cairo/kakarot-ssj/scripts/gen_snapshot.py new file mode 100644 index 000000000..b7b545e6b --- /dev/null +++ b/cairo/kakarot-ssj/scripts/gen_snapshot.py @@ -0,0 +1,18 @@ +import json +import re +import subprocess + +# Execute the command and capture the output +output = subprocess.check_output("scarb cairo-test", shell=True).decode("utf-8") + +# Use regex to capture test names and their associated gas usage +pattern = r"test (.+?) \.\.\. ok \(gas usage est.: (\d+)\)" +matches = re.findall(pattern, output) + +# Convert matches to a dictionary +results = {match[0]: int(match[1]) for match in matches} +sorted_results = {k: results[k] for k in sorted(results.keys())} + +# Dump the results to a JSON file +with open("gas_snapshot.json", "w") as outfile: + json.dump(sorted_results, outfile, indent=4) diff --git a/cairo/kakarot-ssj/scripts/install_hook.sh b/cairo/kakarot-ssj/scripts/install_hook.sh new file mode 100644 index 000000000..f5ab3d834 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/install_hook.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +cat <>.git/hooks/pre-commit +#!/bin/sh + +# Run scarb fmt to format the project. +scarb fmt + +# Check if any files were modified after running scarb fmt. +changed_files=$(git diff --name-only || true) + +if [ -n "${changed_files}" ]; then + echo "The following files were reformatted and staged:" + echo "${changed_files}" + # Stage the changes. + git add ${changed_files} +else + echo "No files were reformatted." +fi + +# Continue with the commit +exit 0 +EOT + +# Check if the current directory is a git repository +if [[ ! -d .git ]]; then + echo "Error: This is not a git repository." + exit 1 +fi + +# Make the hook executable +chmod +x .git/hooks/pre-commit + +echo "pre-commit hook has been installed successfully!" +echo "pre-push hook has been installed successfully!" diff --git a/cairo/kakarot-ssj/scripts/run_filtered_tests.py b/cairo/kakarot-ssj/scripts/run_filtered_tests.py new file mode 100644 index 000000000..4e9c6447e --- /dev/null +++ b/cairo/kakarot-ssj/scripts/run_filtered_tests.py @@ -0,0 +1,104 @@ +import os +import pty +import select +import shutil +import subprocess +import sys +import tempfile +from contextlib import contextmanager +from pathlib import Path + +from filter_tests import filter_tests + +PROJECT_FILES = ["Scarb.toml", "Scarb.lock", ".tool-versions"] + + +@contextmanager +def temporary_project_copy(src_dir): + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + src_path = Path(src_dir) + temp_dir_creation_time = temp_path.stat().st_ctime + + for file in PROJECT_FILES: + if (src_file := src_path / file).exists(): + shutil.copy2(src_file, temp_path / file) + + if (src_crates := src_path / "crates").exists(): + shutil.copytree(src_crates, temp_path / "crates", symlinks=True) + + yield temp_path + + # Copy back only newly created or modified files, excluding build/ directories and .cairo files + for root, dirs, files in os.walk(temp_path): + dirs[:] = [ + d for d in dirs if d != "target" + ] # Don't traverse into build directories + for file in files: + temp_file = Path(root) / file + rel_path = temp_file.relative_to(temp_path) + src_file = src_path / rel_path + + if ( + not src_file.exists() + or temp_file.stat().st_mtime > temp_dir_creation_time + ) and temp_file.suffix != ".cairo": + src_file.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(temp_file, src_file) + print(f"Copied new or modified file: {rel_path}") + + +def stream_output(fd): + while True: + try: + r, _, _ = select.select([fd], [], [], 0.1) + if r: + data = os.read(fd, 1024) + if not data: + break + sys.stdout.buffer.write(data) + sys.stdout.buffer.flush() + except OSError: + break + + +def run_scarb_command(command, cwd): + master, slave = pty.openpty() + print(f"Running command: {command}") + with subprocess.Popen( + command, shell=True, stdout=slave, stderr=slave, close_fds=True, cwd=cwd + ) as process: + os.close(slave) + stream_output(master) + return_code = process.wait() + + if return_code != 0: + print(f"Error: Scarb command failed with return code {return_code}") + sys.exit(return_code) + + +def run_filtered_tests(command): + project_root = Path(__file__).parent.parent + + with temporary_project_copy(project_root) as temp_project_dir: + # Extract the package and filter name from the command + cmd_parts = command.split() + package_index = cmd_parts.index("-p") + 1 + cmd_parts[package_index] + filter_name = cmd_parts[package_index + 1] + + filter_tests(temp_project_dir / "crates", filter_name) + run_scarb_command(command, temp_project_dir) + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python run_filtered_tests.py ") + print("Example: python run_filtered_tests.py scarb test -p evm foo") + print( + "Example: python run_filtered_tests.py snforge test -p evm foo --build-profile" + ) + sys.exit(1) + + full_command = " ".join(sys.argv[1:]) + run_filtered_tests(full_command) diff --git a/cairo/kakarot-ssj/scripts/setup_cairo_native.sh b/cairo/kakarot-ssj/scripts/setup_cairo_native.sh new file mode 100755 index 000000000..4f3101749 --- /dev/null +++ b/cairo/kakarot-ssj/scripts/setup_cairo_native.sh @@ -0,0 +1,187 @@ +#!/bin/bash + +install_essential_deps_linux() { + apt-get update -y + apt-get install -y \ + curl \ + jq \ + ripgrep \ + wget \ + ca-certificates \ + gnupg \ + git +} + +setup_llvm_deps() { + case "$(uname)" in + Darwin) + brew update + brew install llvm@19 + + LIBRARY_PATH=/opt/homebrew/lib + MLIR_SYS_190_PREFIX="$(brew --prefix llvm@19)" + LLVM_SYS_191_PREFIX="${MLIR_SYS_190_PREFIX}" + TABLEGEN_190_PREFIX="${MLIR_SYS_190_PREFIX}" + + export LIBRARY_PATH + export MLIR_SYS_190_PREFIX + export LLVM_SYS_191_PREFIX + export TABLEGEN_190_PREFIX + ;; + Linux) + export DEBIAN_FRONTEND=noninteractive + export TZ=America/New_York + + # shellcheck disable=SC2312 + CODENAME=$(grep VERSION_CODENAME /etc/os-release | cut -d= -f2) + if [[ -z ${CODENAME} ]]; then + echo "Error: Unable to determine OS codename" + exit 1 + fi + + # shellcheck disable=SC2312 + echo "deb http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-19 main" >/etc/apt/sources.list.d/llvm-19.list + echo "deb-src http://apt.llvm.org/${CODENAME}/ llvm-toolchain-${CODENAME}-19 main" >>/etc/apt/sources.list.d/llvm-19.list + # shellcheck disable=SC2312 + if ! wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -; then + echo "Error: Failed to add LLVM GPG key" + exit 1 + fi + + if ! apt-get update && apt-get upgrade -y; then + echo "Error: Failed to update and upgrade packages" + exit 1 + fi + if ! apt-get install -y llvm-19 llvm-19-dev llvm-19-runtime clang-19 clang-tools-19 lld-19 libpolly-19-dev libmlir-19-dev mlir-19-tools; then + echo "Error: Failed to install LLVM packages" + exit 1 + fi + + MLIR_SYS_190_PREFIX=/usr/lib/llvm-19/ + LLVM_SYS_191_PREFIX=/usr/lib/llvm-19/ + TABLEGEN_190_PREFIX=/usr/lib/llvm-19/ + + export MLIR_SYS_190_PREFIX + export LLVM_SYS_191_PREFIX + export TABLEGEN_190_PREFIX + + { + echo "MLIR_SYS_190_PREFIX=${MLIR_SYS_190_PREFIX}" + echo "LLVM_SYS_191_PREFIX=${LLVM_SYS_191_PREFIX}" + echo "TABLEGEN_190_PREFIX=${TABLEGEN_190_PREFIX}" + } >>"${GITHUB_ENV}" + ;; + *) + echo "Error: Unsupported operating system" + exit 1 + ;; + esac + + # GitHub Actions specific + [[ -n ${GITHUB_ACTIONS} ]] && { + { + echo "MLIR_SYS_190_PREFIX=${MLIR_SYS_190_PREFIX}" + echo "LLVM_SYS_191_PREFIX=${LLVM_SYS_191_PREFIX}" + echo "TABLEGEN_190_PREFIX=${TABLEGEN_190_PREFIX}" + } >>"${GITHUB_ENV}" + } +} + +install_rust() { + if command -v cargo >/dev/null 2>&1; then + echo "Rust is already installed with cargo available in PATH." + return 0 + fi + + echo "cargo not found. Installing Rust..." + # shellcheck disable=SC2312 + if ! curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.81.0 --no-modify-path; then + echo >&2 "Failed to install Rust. Aborting." + return 1 + fi + + # shellcheck disable=SC1091 + if [[ -f "${HOME}/.cargo/env" ]]; then + . "${HOME}/.cargo/env" + else + echo >&2 "Failed to find Rust environment file. Aborting." + return 1 + fi + + echo "Rust installed successfully." +} + +install_cairo_native_runtime() { + install_rust || { + echo "Error: Failed to install Rust" + exit 1 + } + + git clone https://github.com/lambdaclass/cairo_native.git + pushd ./cairo_native || exit 1 + git fetch + git checkout ae17dd370a7bbf6affeefb9fa6954965e8b52239 + make deps + make runtime + cp libcairo_native_runtime.a ../libcairo_native_runtime.a + popd || exit 1 + + rm -rf ./cairo_native + + CAIRO_NATIVE_RUNTIME_LIBRARY=$(pwd)/libcairo_native_runtime.a + if [[ -z ${CAIRO_NATIVE_RUNTIME_LIBRARY} ]]; then + echo "Error: Failed to set CAIRO_NATIVE_RUNTIME_LIBRARY" + exit 1 + fi + export CAIRO_NATIVE_RUNTIME_LIBRARY + + echo "CAIRO_NATIVE_RUNTIME_LIBRARY=${CAIRO_NATIVE_RUNTIME_LIBRARY}" + + [[ -n ${GITHUB_ACTIONS} ]] && echo "CAIRO_NATIVE_RUNTIME_LIBRARY=${CAIRO_NATIVE_RUNTIME_LIBRARY}" >>"${GITHUB_ENV}" +} + +main() { + # New argument parsing + SKIP_RUNTIME=false + while getopts ":s" opt; do + case ${opt} in + s) + SKIP_RUNTIME=true + ;; + \?) + echo "Invalid option: ${OPTARG}" 1>&2 + exit 1 + ;; + *) + echo "Error: Unhandled option" 1>&2 + exit 1 + ;; + esac + done + shift $((OPTIND - 1)) + + # shellcheck disable=SC2312 + [[ "$(uname)" == "Linux" ]] && install_essential_deps_linux + + setup_llvm_deps + + if [[ ${SKIP_RUNTIME} == false ]]; then + install_cairo_native_runtime + else + echo "Skipping Cairo native runtime installation" + # Set the environment variable if the library file exists + # shellcheck disable=SC2312 + if [[ -f "$(pwd)/libcairo_native_runtime.a" ]]; then + CAIRO_NATIVE_RUNTIME_LIBRARY=$(pwd)/libcairo_native_runtime.a + export CAIRO_NATIVE_RUNTIME_LIBRARY + echo "CAIRO_NATIVE_RUNTIME_LIBRARY=${CAIRO_NATIVE_RUNTIME_LIBRARY}" + [[ -n ${GITHUB_ACTIONS} ]] && echo "CAIRO_NATIVE_RUNTIME_LIBRARY=${CAIRO_NATIVE_RUNTIME_LIBRARY}" >>"${GITHUB_ENV}" + else + echo "Warning: libcairo_native_runtime.a not found. CAIRO_NATIVE_RUNTIME_LIBRARY not set." + fi + fi + + echo "LLVM and Cairo native runtime dependencies setup completed." +} + +main "$@" diff --git a/cairo/kakarot-ssj/tsconfig.json b/cairo/kakarot-ssj/tsconfig.json new file mode 100644 index 000000000..7556e1d4b --- /dev/null +++ b/cairo/kakarot-ssj/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +} diff --git a/cairo/mock_pragma/.gitignore b/cairo/mock_pragma/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/cairo/mock_pragma/.gitignore @@ -0,0 +1 @@ +target diff --git a/cairo1_contracts/mock_pragma/.tool-versions b/cairo/mock_pragma/.tool-versions similarity index 100% rename from cairo1_contracts/mock_pragma/.tool-versions rename to cairo/mock_pragma/.tool-versions diff --git a/cairo1_contracts/mock_pragma/Scarb.lock b/cairo/mock_pragma/Scarb.lock similarity index 100% rename from cairo1_contracts/mock_pragma/Scarb.lock rename to cairo/mock_pragma/Scarb.lock diff --git a/cairo1_contracts/mock_pragma/Scarb.toml b/cairo/mock_pragma/Scarb.toml similarity index 100% rename from cairo1_contracts/mock_pragma/Scarb.toml rename to cairo/mock_pragma/Scarb.toml diff --git a/cairo1_contracts/mock_pragma/src/lib.cairo b/cairo/mock_pragma/src/lib.cairo similarity index 100% rename from cairo1_contracts/mock_pragma/src/lib.cairo rename to cairo/mock_pragma/src/lib.cairo diff --git a/cairo1_contracts/mock_pragma/src/mock_pragma_oracle.cairo b/cairo/mock_pragma/src/mock_pragma_oracle.cairo similarity index 100% rename from cairo1_contracts/mock_pragma/src/mock_pragma_oracle.cairo rename to cairo/mock_pragma/src/mock_pragma_oracle.cairo diff --git a/cairo/token/.gitignore b/cairo/token/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/cairo/token/.gitignore @@ -0,0 +1 @@ +target diff --git a/cairo1_contracts/token/.tool-versions b/cairo/token/.tool-versions similarity index 100% rename from cairo1_contracts/token/.tool-versions rename to cairo/token/.tool-versions diff --git a/cairo1_contracts/token/Scarb.lock b/cairo/token/Scarb.lock similarity index 100% rename from cairo1_contracts/token/Scarb.lock rename to cairo/token/Scarb.lock diff --git a/cairo1_contracts/token/Scarb.toml b/cairo/token/Scarb.toml similarity index 100% rename from cairo1_contracts/token/Scarb.toml rename to cairo/token/Scarb.toml diff --git a/cairo1_contracts/token/src/lib.cairo b/cairo/token/src/lib.cairo similarity index 100% rename from cairo1_contracts/token/src/lib.cairo rename to cairo/token/src/lib.cairo diff --git a/cairo1_contracts/token/src/starknet_token.cairo b/cairo/token/src/starknet_token.cairo similarity index 100% rename from cairo1_contracts/token/src/starknet_token.cairo rename to cairo/token/src/starknet_token.cairo diff --git a/cairo/utils/.gitignore b/cairo/utils/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/cairo/utils/.gitignore @@ -0,0 +1 @@ +target diff --git a/cairo1_contracts/utils/.tool-versions b/cairo/utils/.tool-versions similarity index 100% rename from cairo1_contracts/utils/.tool-versions rename to cairo/utils/.tool-versions diff --git a/cairo1_contracts/utils/Scarb.lock b/cairo/utils/Scarb.lock similarity index 100% rename from cairo1_contracts/utils/Scarb.lock rename to cairo/utils/Scarb.lock diff --git a/cairo1_contracts/utils/Scarb.toml b/cairo/utils/Scarb.toml similarity index 100% rename from cairo1_contracts/utils/Scarb.toml rename to cairo/utils/Scarb.toml diff --git a/cairo1_contracts/utils/src/benchmark_cairo_calls.cairo b/cairo/utils/src/benchmark_cairo_calls.cairo similarity index 100% rename from cairo1_contracts/utils/src/benchmark_cairo_calls.cairo rename to cairo/utils/src/benchmark_cairo_calls.cairo diff --git a/cairo1_contracts/utils/src/lib.cairo b/cairo/utils/src/lib.cairo similarity index 100% rename from cairo1_contracts/utils/src/lib.cairo rename to cairo/utils/src/lib.cairo diff --git a/cairo1_contracts/utils/src/universal_library_caller.cairo b/cairo/utils/src/universal_library_caller.cairo similarity index 100% rename from cairo1_contracts/utils/src/universal_library_caller.cairo rename to cairo/utils/src/universal_library_caller.cairo diff --git a/src/backend/starknet.cairo b/cairo_zero/backend/starknet.cairo similarity index 100% rename from src/backend/starknet.cairo rename to cairo_zero/backend/starknet.cairo diff --git a/src/kakarot/account.cairo b/cairo_zero/kakarot/account.cairo similarity index 100% rename from src/kakarot/account.cairo rename to cairo_zero/kakarot/account.cairo diff --git a/src/kakarot/accounts/account_contract.cairo b/cairo_zero/kakarot/accounts/account_contract.cairo similarity index 100% rename from src/kakarot/accounts/account_contract.cairo rename to cairo_zero/kakarot/accounts/account_contract.cairo diff --git a/src/kakarot/accounts/library.cairo b/cairo_zero/kakarot/accounts/library.cairo similarity index 100% rename from src/kakarot/accounts/library.cairo rename to cairo_zero/kakarot/accounts/library.cairo diff --git a/src/kakarot/accounts/model.cairo b/cairo_zero/kakarot/accounts/model.cairo similarity index 100% rename from src/kakarot/accounts/model.cairo rename to cairo_zero/kakarot/accounts/model.cairo diff --git a/src/kakarot/accounts/uninitialized_account.cairo b/cairo_zero/kakarot/accounts/uninitialized_account.cairo similarity index 100% rename from src/kakarot/accounts/uninitialized_account.cairo rename to cairo_zero/kakarot/accounts/uninitialized_account.cairo diff --git a/src/kakarot/constants.cairo b/cairo_zero/kakarot/constants.cairo similarity index 100% rename from src/kakarot/constants.cairo rename to cairo_zero/kakarot/constants.cairo diff --git a/src/kakarot/errors.cairo b/cairo_zero/kakarot/errors.cairo similarity index 100% rename from src/kakarot/errors.cairo rename to cairo_zero/kakarot/errors.cairo diff --git a/src/kakarot/eth_rpc.cairo b/cairo_zero/kakarot/eth_rpc.cairo similarity index 100% rename from src/kakarot/eth_rpc.cairo rename to cairo_zero/kakarot/eth_rpc.cairo diff --git a/src/kakarot/events.cairo b/cairo_zero/kakarot/events.cairo similarity index 100% rename from src/kakarot/events.cairo rename to cairo_zero/kakarot/events.cairo diff --git a/src/kakarot/evm.cairo b/cairo_zero/kakarot/evm.cairo similarity index 100% rename from src/kakarot/evm.cairo rename to cairo_zero/kakarot/evm.cairo diff --git a/src/kakarot/gas.cairo b/cairo_zero/kakarot/gas.cairo similarity index 100% rename from src/kakarot/gas.cairo rename to cairo_zero/kakarot/gas.cairo diff --git a/src/kakarot/instructions/block_information.cairo b/cairo_zero/kakarot/instructions/block_information.cairo similarity index 100% rename from src/kakarot/instructions/block_information.cairo rename to cairo_zero/kakarot/instructions/block_information.cairo diff --git a/src/kakarot/instructions/duplication_operations.cairo b/cairo_zero/kakarot/instructions/duplication_operations.cairo similarity index 100% rename from src/kakarot/instructions/duplication_operations.cairo rename to cairo_zero/kakarot/instructions/duplication_operations.cairo diff --git a/src/kakarot/instructions/environmental_information.cairo b/cairo_zero/kakarot/instructions/environmental_information.cairo similarity index 100% rename from src/kakarot/instructions/environmental_information.cairo rename to cairo_zero/kakarot/instructions/environmental_information.cairo diff --git a/src/kakarot/instructions/exchange_operations.cairo b/cairo_zero/kakarot/instructions/exchange_operations.cairo similarity index 100% rename from src/kakarot/instructions/exchange_operations.cairo rename to cairo_zero/kakarot/instructions/exchange_operations.cairo diff --git a/src/kakarot/instructions/logging_operations.cairo b/cairo_zero/kakarot/instructions/logging_operations.cairo similarity index 100% rename from src/kakarot/instructions/logging_operations.cairo rename to cairo_zero/kakarot/instructions/logging_operations.cairo diff --git a/src/kakarot/instructions/memory_operations.cairo b/cairo_zero/kakarot/instructions/memory_operations.cairo similarity index 100% rename from src/kakarot/instructions/memory_operations.cairo rename to cairo_zero/kakarot/instructions/memory_operations.cairo diff --git a/src/kakarot/instructions/push_operations.cairo b/cairo_zero/kakarot/instructions/push_operations.cairo similarity index 100% rename from src/kakarot/instructions/push_operations.cairo rename to cairo_zero/kakarot/instructions/push_operations.cairo diff --git a/src/kakarot/instructions/sha3.cairo b/cairo_zero/kakarot/instructions/sha3.cairo similarity index 100% rename from src/kakarot/instructions/sha3.cairo rename to cairo_zero/kakarot/instructions/sha3.cairo diff --git a/src/kakarot/instructions/stop_and_math_operations.cairo b/cairo_zero/kakarot/instructions/stop_and_math_operations.cairo similarity index 100% rename from src/kakarot/instructions/stop_and_math_operations.cairo rename to cairo_zero/kakarot/instructions/stop_and_math_operations.cairo diff --git a/src/kakarot/instructions/system_operations.cairo b/cairo_zero/kakarot/instructions/system_operations.cairo similarity index 100% rename from src/kakarot/instructions/system_operations.cairo rename to cairo_zero/kakarot/instructions/system_operations.cairo diff --git a/src/kakarot/interfaces/interfaces.cairo b/cairo_zero/kakarot/interfaces/interfaces.cairo similarity index 100% rename from src/kakarot/interfaces/interfaces.cairo rename to cairo_zero/kakarot/interfaces/interfaces.cairo diff --git a/src/kakarot/interpreter.cairo b/cairo_zero/kakarot/interpreter.cairo similarity index 100% rename from src/kakarot/interpreter.cairo rename to cairo_zero/kakarot/interpreter.cairo diff --git a/src/kakarot/kakarot.cairo b/cairo_zero/kakarot/kakarot.cairo similarity index 100% rename from src/kakarot/kakarot.cairo rename to cairo_zero/kakarot/kakarot.cairo diff --git a/src/kakarot/library.cairo b/cairo_zero/kakarot/library.cairo similarity index 100% rename from src/kakarot/library.cairo rename to cairo_zero/kakarot/library.cairo diff --git a/src/kakarot/memory.cairo b/cairo_zero/kakarot/memory.cairo similarity index 100% rename from src/kakarot/memory.cairo rename to cairo_zero/kakarot/memory.cairo diff --git a/src/kakarot/model.cairo b/cairo_zero/kakarot/model.cairo similarity index 100% rename from src/kakarot/model.cairo rename to cairo_zero/kakarot/model.cairo diff --git a/src/kakarot/precompiles/blake2f.cairo b/cairo_zero/kakarot/precompiles/blake2f.cairo similarity index 100% rename from src/kakarot/precompiles/blake2f.cairo rename to cairo_zero/kakarot/precompiles/blake2f.cairo diff --git a/src/kakarot/precompiles/ec_recover.cairo b/cairo_zero/kakarot/precompiles/ec_recover.cairo similarity index 100% rename from src/kakarot/precompiles/ec_recover.cairo rename to cairo_zero/kakarot/precompiles/ec_recover.cairo diff --git a/src/kakarot/precompiles/identity.cairo b/cairo_zero/kakarot/precompiles/identity.cairo similarity index 100% rename from src/kakarot/precompiles/identity.cairo rename to cairo_zero/kakarot/precompiles/identity.cairo diff --git a/src/kakarot/precompiles/kakarot_precompiles.cairo b/cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo similarity index 100% rename from src/kakarot/precompiles/kakarot_precompiles.cairo rename to cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo diff --git a/src/kakarot/precompiles/p256verify.cairo b/cairo_zero/kakarot/precompiles/p256verify.cairo similarity index 100% rename from src/kakarot/precompiles/p256verify.cairo rename to cairo_zero/kakarot/precompiles/p256verify.cairo diff --git a/src/kakarot/precompiles/precompiles.cairo b/cairo_zero/kakarot/precompiles/precompiles.cairo similarity index 100% rename from src/kakarot/precompiles/precompiles.cairo rename to cairo_zero/kakarot/precompiles/precompiles.cairo diff --git a/src/kakarot/precompiles/precompiles_helpers.cairo b/cairo_zero/kakarot/precompiles/precompiles_helpers.cairo similarity index 100% rename from src/kakarot/precompiles/precompiles_helpers.cairo rename to cairo_zero/kakarot/precompiles/precompiles_helpers.cairo diff --git a/src/kakarot/precompiles/ripemd160.cairo b/cairo_zero/kakarot/precompiles/ripemd160.cairo similarity index 100% rename from src/kakarot/precompiles/ripemd160.cairo rename to cairo_zero/kakarot/precompiles/ripemd160.cairo diff --git a/src/kakarot/precompiles/sha256.cairo b/cairo_zero/kakarot/precompiles/sha256.cairo similarity index 100% rename from src/kakarot/precompiles/sha256.cairo rename to cairo_zero/kakarot/precompiles/sha256.cairo diff --git a/src/kakarot/stack.cairo b/cairo_zero/kakarot/stack.cairo similarity index 100% rename from src/kakarot/stack.cairo rename to cairo_zero/kakarot/stack.cairo diff --git a/src/kakarot/state.cairo b/cairo_zero/kakarot/state.cairo similarity index 100% rename from src/kakarot/state.cairo rename to cairo_zero/kakarot/state.cairo diff --git a/src/kakarot/storages.cairo b/cairo_zero/kakarot/storages.cairo similarity index 100% rename from src/kakarot/storages.cairo rename to cairo_zero/kakarot/storages.cairo diff --git a/src/openzeppelin/ERC20.cairo b/cairo_zero/openzeppelin/ERC20.cairo similarity index 100% rename from src/openzeppelin/ERC20.cairo rename to cairo_zero/openzeppelin/ERC20.cairo diff --git a/cairo_zero/tests/conftest.py b/cairo_zero/tests/conftest.py new file mode 100644 index 000000000..8b116a7c9 --- /dev/null +++ b/cairo_zero/tests/conftest.py @@ -0,0 +1,6 @@ +# ruff: noqa: F403 + +# Import everything from the root conftest.py +from tests.conftest import * +from tests.fixtures import * +from tests.utils import * diff --git a/tests/src/backend/test_starknet.cairo b/cairo_zero/tests/src/backend/test_starknet.cairo similarity index 100% rename from tests/src/backend/test_starknet.cairo rename to cairo_zero/tests/src/backend/test_starknet.cairo diff --git a/tests/src/backend/test_starknet.py b/cairo_zero/tests/src/backend/test_starknet.py similarity index 100% rename from tests/src/backend/test_starknet.py rename to cairo_zero/tests/src/backend/test_starknet.py diff --git a/tests/src/conftest.py b/cairo_zero/tests/src/conftest.py similarity index 100% rename from tests/src/conftest.py rename to cairo_zero/tests/src/conftest.py diff --git a/tests/src/kakarot/__init__.py b/cairo_zero/tests/src/kakarot/__init__.py similarity index 100% rename from tests/src/kakarot/__init__.py rename to cairo_zero/tests/src/kakarot/__init__.py diff --git a/tests/src/kakarot/accounts/test_account_contract.cairo b/cairo_zero/tests/src/kakarot/accounts/test_account_contract.cairo similarity index 100% rename from tests/src/kakarot/accounts/test_account_contract.cairo rename to cairo_zero/tests/src/kakarot/accounts/test_account_contract.cairo diff --git a/tests/src/kakarot/accounts/test_account_contract.py b/cairo_zero/tests/src/kakarot/accounts/test_account_contract.py similarity index 100% rename from tests/src/kakarot/accounts/test_account_contract.py rename to cairo_zero/tests/src/kakarot/accounts/test_account_contract.py diff --git a/tests/src/kakarot/instructions/test_block_information.cairo b/cairo_zero/tests/src/kakarot/instructions/test_block_information.cairo similarity index 100% rename from tests/src/kakarot/instructions/test_block_information.cairo rename to cairo_zero/tests/src/kakarot/instructions/test_block_information.cairo diff --git a/tests/src/kakarot/instructions/test_block_information.py b/cairo_zero/tests/src/kakarot/instructions/test_block_information.py similarity index 100% rename from tests/src/kakarot/instructions/test_block_information.py rename to cairo_zero/tests/src/kakarot/instructions/test_block_information.py diff --git a/tests/src/kakarot/instructions/test_duplication_operations.cairo b/cairo_zero/tests/src/kakarot/instructions/test_duplication_operations.cairo similarity index 100% rename from tests/src/kakarot/instructions/test_duplication_operations.cairo rename to cairo_zero/tests/src/kakarot/instructions/test_duplication_operations.cairo diff --git a/tests/src/kakarot/instructions/test_duplication_operations.py b/cairo_zero/tests/src/kakarot/instructions/test_duplication_operations.py similarity index 100% rename from tests/src/kakarot/instructions/test_duplication_operations.py rename to cairo_zero/tests/src/kakarot/instructions/test_duplication_operations.py diff --git a/tests/src/kakarot/instructions/test_environmental_information.cairo b/cairo_zero/tests/src/kakarot/instructions/test_environmental_information.cairo similarity index 100% rename from tests/src/kakarot/instructions/test_environmental_information.cairo rename to cairo_zero/tests/src/kakarot/instructions/test_environmental_information.cairo diff --git a/tests/src/kakarot/instructions/test_environmental_information.py b/cairo_zero/tests/src/kakarot/instructions/test_environmental_information.py similarity index 100% rename from tests/src/kakarot/instructions/test_environmental_information.py rename to cairo_zero/tests/src/kakarot/instructions/test_environmental_information.py diff --git a/tests/src/kakarot/instructions/test_exchange_operations.cairo b/cairo_zero/tests/src/kakarot/instructions/test_exchange_operations.cairo similarity index 100% rename from tests/src/kakarot/instructions/test_exchange_operations.cairo rename to cairo_zero/tests/src/kakarot/instructions/test_exchange_operations.cairo diff --git a/tests/src/kakarot/instructions/test_exchange_operations.py b/cairo_zero/tests/src/kakarot/instructions/test_exchange_operations.py similarity index 100% rename from tests/src/kakarot/instructions/test_exchange_operations.py rename to cairo_zero/tests/src/kakarot/instructions/test_exchange_operations.py diff --git a/tests/src/kakarot/instructions/test_memory_operations.cairo b/cairo_zero/tests/src/kakarot/instructions/test_memory_operations.cairo similarity index 100% rename from tests/src/kakarot/instructions/test_memory_operations.cairo rename to cairo_zero/tests/src/kakarot/instructions/test_memory_operations.cairo diff --git a/tests/src/kakarot/instructions/test_memory_operations.py b/cairo_zero/tests/src/kakarot/instructions/test_memory_operations.py similarity index 100% rename from tests/src/kakarot/instructions/test_memory_operations.py rename to cairo_zero/tests/src/kakarot/instructions/test_memory_operations.py diff --git a/tests/src/kakarot/instructions/test_push_operations.cairo b/cairo_zero/tests/src/kakarot/instructions/test_push_operations.cairo similarity index 100% rename from tests/src/kakarot/instructions/test_push_operations.cairo rename to cairo_zero/tests/src/kakarot/instructions/test_push_operations.cairo diff --git a/tests/src/kakarot/instructions/test_push_operations.py b/cairo_zero/tests/src/kakarot/instructions/test_push_operations.py similarity index 100% rename from tests/src/kakarot/instructions/test_push_operations.py rename to cairo_zero/tests/src/kakarot/instructions/test_push_operations.py diff --git a/tests/src/kakarot/instructions/test_stop_and_math_operations.cairo b/cairo_zero/tests/src/kakarot/instructions/test_stop_and_math_operations.cairo similarity index 100% rename from tests/src/kakarot/instructions/test_stop_and_math_operations.cairo rename to cairo_zero/tests/src/kakarot/instructions/test_stop_and_math_operations.cairo diff --git a/tests/src/kakarot/instructions/test_stop_and_math_operations.py b/cairo_zero/tests/src/kakarot/instructions/test_stop_and_math_operations.py similarity index 100% rename from tests/src/kakarot/instructions/test_stop_and_math_operations.py rename to cairo_zero/tests/src/kakarot/instructions/test_stop_and_math_operations.py diff --git a/tests/src/kakarot/precompiles/test_blake2f.cairo b/cairo_zero/tests/src/kakarot/precompiles/test_blake2f.cairo similarity index 100% rename from tests/src/kakarot/precompiles/test_blake2f.cairo rename to cairo_zero/tests/src/kakarot/precompiles/test_blake2f.cairo diff --git a/tests/src/kakarot/precompiles/test_blake2f.py b/cairo_zero/tests/src/kakarot/precompiles/test_blake2f.py similarity index 100% rename from tests/src/kakarot/precompiles/test_blake2f.py rename to cairo_zero/tests/src/kakarot/precompiles/test_blake2f.py diff --git a/tests/src/kakarot/precompiles/test_datacopy.cairo b/cairo_zero/tests/src/kakarot/precompiles/test_datacopy.cairo similarity index 100% rename from tests/src/kakarot/precompiles/test_datacopy.cairo rename to cairo_zero/tests/src/kakarot/precompiles/test_datacopy.cairo diff --git a/tests/src/kakarot/precompiles/test_datacopy.py b/cairo_zero/tests/src/kakarot/precompiles/test_datacopy.py similarity index 100% rename from tests/src/kakarot/precompiles/test_datacopy.py rename to cairo_zero/tests/src/kakarot/precompiles/test_datacopy.py diff --git a/tests/src/kakarot/precompiles/test_ec_recover.cairo b/cairo_zero/tests/src/kakarot/precompiles/test_ec_recover.cairo similarity index 100% rename from tests/src/kakarot/precompiles/test_ec_recover.cairo rename to cairo_zero/tests/src/kakarot/precompiles/test_ec_recover.cairo diff --git a/tests/src/kakarot/precompiles/test_ec_recover.py b/cairo_zero/tests/src/kakarot/precompiles/test_ec_recover.py similarity index 100% rename from tests/src/kakarot/precompiles/test_ec_recover.py rename to cairo_zero/tests/src/kakarot/precompiles/test_ec_recover.py diff --git a/tests/src/kakarot/precompiles/test_precompiles.cairo b/cairo_zero/tests/src/kakarot/precompiles/test_precompiles.cairo similarity index 100% rename from tests/src/kakarot/precompiles/test_precompiles.cairo rename to cairo_zero/tests/src/kakarot/precompiles/test_precompiles.cairo diff --git a/tests/src/kakarot/precompiles/test_precompiles.py b/cairo_zero/tests/src/kakarot/precompiles/test_precompiles.py similarity index 100% rename from tests/src/kakarot/precompiles/test_precompiles.py rename to cairo_zero/tests/src/kakarot/precompiles/test_precompiles.py diff --git a/tests/src/kakarot/precompiles/test_ripemd160.cairo b/cairo_zero/tests/src/kakarot/precompiles/test_ripemd160.cairo similarity index 100% rename from tests/src/kakarot/precompiles/test_ripemd160.cairo rename to cairo_zero/tests/src/kakarot/precompiles/test_ripemd160.cairo diff --git a/tests/src/kakarot/precompiles/test_ripemd160.py b/cairo_zero/tests/src/kakarot/precompiles/test_ripemd160.py similarity index 100% rename from tests/src/kakarot/precompiles/test_ripemd160.py rename to cairo_zero/tests/src/kakarot/precompiles/test_ripemd160.py diff --git a/tests/src/kakarot/precompiles/test_sha256.cairo b/cairo_zero/tests/src/kakarot/precompiles/test_sha256.cairo similarity index 100% rename from tests/src/kakarot/precompiles/test_sha256.cairo rename to cairo_zero/tests/src/kakarot/precompiles/test_sha256.cairo diff --git a/tests/src/kakarot/precompiles/test_sha256.py b/cairo_zero/tests/src/kakarot/precompiles/test_sha256.py similarity index 100% rename from tests/src/kakarot/precompiles/test_sha256.py rename to cairo_zero/tests/src/kakarot/precompiles/test_sha256.py diff --git a/tests/src/kakarot/test_account.cairo b/cairo_zero/tests/src/kakarot/test_account.cairo similarity index 100% rename from tests/src/kakarot/test_account.cairo rename to cairo_zero/tests/src/kakarot/test_account.cairo diff --git a/tests/src/kakarot/test_account.py b/cairo_zero/tests/src/kakarot/test_account.py similarity index 100% rename from tests/src/kakarot/test_account.py rename to cairo_zero/tests/src/kakarot/test_account.py diff --git a/tests/src/kakarot/test_evm.cairo b/cairo_zero/tests/src/kakarot/test_evm.cairo similarity index 100% rename from tests/src/kakarot/test_evm.cairo rename to cairo_zero/tests/src/kakarot/test_evm.cairo diff --git a/tests/src/kakarot/test_evm.py b/cairo_zero/tests/src/kakarot/test_evm.py similarity index 100% rename from tests/src/kakarot/test_evm.py rename to cairo_zero/tests/src/kakarot/test_evm.py diff --git a/tests/src/kakarot/test_gas.cairo b/cairo_zero/tests/src/kakarot/test_gas.cairo similarity index 100% rename from tests/src/kakarot/test_gas.cairo rename to cairo_zero/tests/src/kakarot/test_gas.cairo diff --git a/tests/src/kakarot/test_gas.py b/cairo_zero/tests/src/kakarot/test_gas.py similarity index 100% rename from tests/src/kakarot/test_gas.py rename to cairo_zero/tests/src/kakarot/test_gas.py diff --git a/tests/src/kakarot/test_kakarot.cairo b/cairo_zero/tests/src/kakarot/test_kakarot.cairo similarity index 100% rename from tests/src/kakarot/test_kakarot.cairo rename to cairo_zero/tests/src/kakarot/test_kakarot.cairo diff --git a/tests/src/kakarot/test_kakarot.py b/cairo_zero/tests/src/kakarot/test_kakarot.py similarity index 100% rename from tests/src/kakarot/test_kakarot.py rename to cairo_zero/tests/src/kakarot/test_kakarot.py diff --git a/tests/src/kakarot/test_memory.cairo b/cairo_zero/tests/src/kakarot/test_memory.cairo similarity index 100% rename from tests/src/kakarot/test_memory.cairo rename to cairo_zero/tests/src/kakarot/test_memory.cairo diff --git a/tests/src/kakarot/test_memory.py b/cairo_zero/tests/src/kakarot/test_memory.py similarity index 100% rename from tests/src/kakarot/test_memory.py rename to cairo_zero/tests/src/kakarot/test_memory.py diff --git a/tests/src/kakarot/test_stack.cairo b/cairo_zero/tests/src/kakarot/test_stack.cairo similarity index 100% rename from tests/src/kakarot/test_stack.cairo rename to cairo_zero/tests/src/kakarot/test_stack.cairo diff --git a/tests/src/kakarot/test_stack.py b/cairo_zero/tests/src/kakarot/test_stack.py similarity index 100% rename from tests/src/kakarot/test_stack.py rename to cairo_zero/tests/src/kakarot/test_stack.py diff --git a/tests/src/kakarot/test_state.cairo b/cairo_zero/tests/src/kakarot/test_state.cairo similarity index 100% rename from tests/src/kakarot/test_state.cairo rename to cairo_zero/tests/src/kakarot/test_state.cairo diff --git a/tests/src/kakarot/test_state.py b/cairo_zero/tests/src/kakarot/test_state.py similarity index 100% rename from tests/src/kakarot/test_state.py rename to cairo_zero/tests/src/kakarot/test_state.py diff --git a/tests/src/utils/test_array.cairo b/cairo_zero/tests/src/utils/test_array.cairo similarity index 100% rename from tests/src/utils/test_array.cairo rename to cairo_zero/tests/src/utils/test_array.cairo diff --git a/tests/src/utils/test_array.py b/cairo_zero/tests/src/utils/test_array.py similarity index 100% rename from tests/src/utils/test_array.py rename to cairo_zero/tests/src/utils/test_array.py diff --git a/tests/src/utils/test_bytes.cairo b/cairo_zero/tests/src/utils/test_bytes.cairo similarity index 100% rename from tests/src/utils/test_bytes.cairo rename to cairo_zero/tests/src/utils/test_bytes.cairo diff --git a/tests/src/utils/test_bytes.py b/cairo_zero/tests/src/utils/test_bytes.py similarity index 100% rename from tests/src/utils/test_bytes.py rename to cairo_zero/tests/src/utils/test_bytes.py diff --git a/tests/src/utils/test_dict.cairo b/cairo_zero/tests/src/utils/test_dict.cairo similarity index 100% rename from tests/src/utils/test_dict.cairo rename to cairo_zero/tests/src/utils/test_dict.cairo diff --git a/tests/src/utils/test_dict.py b/cairo_zero/tests/src/utils/test_dict.py similarity index 100% rename from tests/src/utils/test_dict.py rename to cairo_zero/tests/src/utils/test_dict.py diff --git a/tests/src/utils/test_eth_transaction.cairo b/cairo_zero/tests/src/utils/test_eth_transaction.cairo similarity index 100% rename from tests/src/utils/test_eth_transaction.cairo rename to cairo_zero/tests/src/utils/test_eth_transaction.cairo diff --git a/tests/src/utils/test_eth_transaction.py b/cairo_zero/tests/src/utils/test_eth_transaction.py similarity index 100% rename from tests/src/utils/test_eth_transaction.py rename to cairo_zero/tests/src/utils/test_eth_transaction.py diff --git a/tests/src/utils/test_rlp.cairo b/cairo_zero/tests/src/utils/test_rlp.cairo similarity index 100% rename from tests/src/utils/test_rlp.cairo rename to cairo_zero/tests/src/utils/test_rlp.cairo diff --git a/tests/src/utils/test_rlp.py b/cairo_zero/tests/src/utils/test_rlp.py similarity index 98% rename from tests/src/utils/test_rlp.py rename to cairo_zero/tests/src/utils/test_rlp.py index 10a029369..7180b1730 100644 --- a/tests/src/utils/test_rlp.py +++ b/cairo_zero/tests/src/utils/test_rlp.py @@ -21,7 +21,7 @@ def test_should_match_prefix_reference_implementation(self, cairo_run, data): expected_len, expected_offset, ] = codec.consume_length_prefix(encoded_data, 0) - expected_type = 0 if rlp_type == bytes else 1 + expected_type = 0 if rlp_type is bytes else 1 output = cairo_run("test__decode_type", data=list(encoded_data)) diff --git a/tests/src/utils/test_uint256.cairo b/cairo_zero/tests/src/utils/test_uint256.cairo similarity index 100% rename from tests/src/utils/test_uint256.cairo rename to cairo_zero/tests/src/utils/test_uint256.cairo diff --git a/tests/src/utils/test_uint256.py b/cairo_zero/tests/src/utils/test_uint256.py similarity index 100% rename from tests/src/utils/test_uint256.py rename to cairo_zero/tests/src/utils/test_uint256.py diff --git a/tests/src/utils/test_utils.cairo b/cairo_zero/tests/src/utils/test_utils.cairo similarity index 100% rename from tests/src/utils/test_utils.cairo rename to cairo_zero/tests/src/utils/test_utils.cairo diff --git a/tests/src/utils/test_utils.py b/cairo_zero/tests/src/utils/test_utils.py similarity index 100% rename from tests/src/utils/test_utils.py rename to cairo_zero/tests/src/utils/test_utils.py diff --git a/src/utils/OpenzeppelinAccount.cairo b/cairo_zero/utils/OpenzeppelinAccount.cairo similarity index 100% rename from src/utils/OpenzeppelinAccount.cairo rename to cairo_zero/utils/OpenzeppelinAccount.cairo diff --git a/src/utils/array.cairo b/cairo_zero/utils/array.cairo similarity index 100% rename from src/utils/array.cairo rename to cairo_zero/utils/array.cairo diff --git a/src/utils/bytes.cairo b/cairo_zero/utils/bytes.cairo similarity index 100% rename from src/utils/bytes.cairo rename to cairo_zero/utils/bytes.cairo diff --git a/src/utils/dict.cairo b/cairo_zero/utils/dict.cairo similarity index 100% rename from src/utils/dict.cairo rename to cairo_zero/utils/dict.cairo diff --git a/src/utils/eth_transaction.cairo b/cairo_zero/utils/eth_transaction.cairo similarity index 100% rename from src/utils/eth_transaction.cairo rename to cairo_zero/utils/eth_transaction.cairo diff --git a/src/utils/maths.cairo b/cairo_zero/utils/maths.cairo similarity index 100% rename from src/utils/maths.cairo rename to cairo_zero/utils/maths.cairo diff --git a/src/utils/rlp.cairo b/cairo_zero/utils/rlp.cairo similarity index 100% rename from src/utils/rlp.cairo rename to cairo_zero/utils/rlp.cairo diff --git a/src/utils/signature.cairo b/cairo_zero/utils/signature.cairo similarity index 100% rename from src/utils/signature.cairo rename to cairo_zero/utils/signature.cairo diff --git a/src/utils/uint256.cairo b/cairo_zero/utils/uint256.cairo similarity index 100% rename from src/utils/uint256.cairo rename to cairo_zero/utils/uint256.cairo diff --git a/src/utils/utils.cairo b/cairo_zero/utils/utils.cairo similarity index 100% rename from src/utils/utils.cairo rename to cairo_zero/utils/utils.cairo diff --git a/docker/deployer/Dockerfile b/docker/deployer/Dockerfile index 895f9eed6..55cadaf4f 100644 --- a/docker/deployer/Dockerfile +++ b/docker/deployer/Dockerfile @@ -14,14 +14,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git \ && rm -rf /var/lib/apt/lists/* -ENV POETRY_NO_INTERACTION=1 \ - POETRY_VIRTUALENVS_IN_PROJECT=1 \ - POETRY_VIRTUALENVS_CREATE=1 \ - VIRTUAL_ENV=/app/kakarot/.venv \ +ENV VIRTUAL_ENV=/app/kakarot/.venv \ PATH="/app/kakarot/.venv/bin:/root/.local/bin:$PATH" RUN --mount=type=cache,target=/root/.cache \ - curl -sSL https://install.python-poetry.org | python3 - + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +RUN --mount=type=cache,target=/root/.cache \ +curl -LsSf https://astral.sh/uv/install.sh | sh WORKDIR /app/kakarot @@ -52,31 +52,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ARG GITHUB_TOKEN ENV GITHUB_TOKEN=${GITHUB_TOKEN} -# install dependencies -COPY poetry.lock . -COPY pyproject.toml . -COPY kakarot_scripts ./kakarot_scripts -COPY README.md . -COPY tests ./tests - -RUN --mount=type=cache,target=/root/.cache \ - poetry install - -# split install in two steps to leverage docker cache COPY . . +SHELL ["/bin/bash", "-c"] +RUN source "$HOME/.cargo/env" && uv venv && uv pip install -e . # Install asdf for multiple scarb versions RUN git clone --depth 1 https://github.com/asdf-vm/asdf.git "$HOME/.asdf" && \ echo ". $HOME/.asdf/asdf.sh" >> "$HOME/.bashrc" && \ echo ". $HOME/.asdf/asdf.sh" >> "$HOME/.profile" -SHELL ["/bin/bash", "-c"] -RUN source "$HOME/.asdf/asdf.sh" && asdf plugin add scarb && asdf install scarb 0.7.0 && asdf install scarb 2.6.5 - +RUN source "$HOME/.asdf/asdf.sh" && asdf plugin add scarb && asdf install scarb 0.7.0 && asdf install scarb 2.6.5 && asdf install scarb 2.8.3 RUN --mount=type=cache,target=/root/.cache \ - source "$HOME/.asdf/asdf.sh" && make fetch-ssj-artifacts && make build && make build-sol + source "$HOME/.asdf/asdf.sh" && source "$HOME/.cargo/env" && make build-ssj && make build && make build-sol ############################################ @@ -87,22 +76,20 @@ FROM base as deployer COPY --from=builder /app/kakarot/build ./build/ COPY --from=builder /app/kakarot/kakarot_scripts/ ./kakarot_scripts/ COPY --from=builder /app/kakarot/tests ./tests/ +COPY --from=builder /app/kakarot/.venv ./.venv # Cairo Smart contracts are used in deploy_kakarot.py # To limit the probability of this Dockerfile to break, we copy the entire src and not individual files -COPY --from=builder /app/kakarot/src ./src/ +COPY --from=builder /app/kakarot/cairo_zero/ ./cairo_zero/ # Default Solidity contracts are also used in deploy_kakarot.py COPY --from=builder /app/kakarot/solidity_contracts ./solidity_contracts/ COPY --from=builder /app/kakarot/Makefile . -COPY --from=builder /app/kakarot/poetry.lock . +COPY --from=builder /app/kakarot/uv.lock . COPY --from=builder /app/kakarot/pyproject.toml . COPY --from=builder /app/kakarot/foundry.toml . COPY --from=builder /app/kakarot/README.md . -RUN --mount=type=cache,target=/root/.cache \ - poetry install --without dev - # Deploy kakarot -CMD ["python", "kakarot_scripts/deploy_kakarot.py"] +CMD ["uv", "run", "deploy"] diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index 6e5e83668..e9d212b08 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -6,7 +6,7 @@ In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and -expression, level of experience, education, socio-economic status, nationality, +expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards diff --git a/docs/img/codespaces.png b/docs/img/codespaces.png index ea9e1e06b..19bef8040 100644 Binary files a/docs/img/codespaces.png and b/docs/img/codespaces.png differ diff --git a/experimental_contracts/foundry.toml b/experimental_contracts/foundry.toml deleted file mode 100644 index d2f13274a..000000000 --- a/experimental_contracts/foundry.toml +++ /dev/null @@ -1,9 +0,0 @@ -[profile.default] -src = 'src' -test = 'tests' -out = '../solidity_contracts/build' -libs = ['../lib'] - -[rpc_endpoints] -anvil = "http://127.0.0.1:8545" -kakarot = "http://127.0.0.1:3030" diff --git a/kakarot_scripts/constants.py b/kakarot_scripts/constants.py index 61dfa7029..676cc59ef 100644 --- a/kakarot_scripts/constants.py +++ b/kakarot_scripts/constants.py @@ -218,16 +218,18 @@ class ChainId(IntEnum): or "0x20eB005C0b9c906691F885eca5895338E15c36De", # Defaults to faucet on appchain sepolia 16, ) -CAIRO_ZERO_DIR = Path("src") -CAIRO_DIR = Path("cairo1_contracts") -TESTS_DIR = Path("tests") +CAIRO_ZERO_DIR = Path("cairo_zero") +CAIRO_DIR = Path("cairo") +TESTS_DIR_CAIRO_ZERO = Path("cairo_zero/tests") +TESTS_DIR_END_TO_END = Path("tests") CONTRACTS = { p.stem: p for p in ( list(CAIRO_ZERO_DIR.glob("**/*.cairo")) - + list(TESTS_DIR.glob("**/*.cairo")) - + list(CAIRO_DIR.glob("**/*.cairo")) + + list(TESTS_DIR_CAIRO_ZERO.glob("**/*.cairo")) + + list(TESTS_DIR_END_TO_END.glob("**/*.cairo")) + + [x for x in list(CAIRO_DIR.glob("**/*.cairo")) if "kakarot-ssj" not in str(x)] ) } diff --git a/kakarot_scripts/utils/starknet.py b/kakarot_scripts/utils/starknet.py index aef217e78..a52e55965 100644 --- a/kakarot_scripts/utils/starknet.py +++ b/kakarot_scripts/utils/starknet.py @@ -249,9 +249,11 @@ def get_artifact(contract_name): return Artifact(sierra=None, casm=artifacts[0]) # Cairo 1 artifacts - artifacts = list(CAIRO_DIR.glob(f"**/*{contract_name}.*.json")) or list( - BUILD_DIR_SSJ.glob(f"**/*{contract_name}.*.json") - ) + artifacts = [ + artifact + for artifact in list(CAIRO_DIR.glob(f"**/*{contract_name}.*.json")) + if "test" not in str(artifact) + ] or list(BUILD_DIR_SSJ.glob(f"**/*{contract_name}.*.json")) if artifacts: sierra, casm = ( artifacts diff --git a/pyproject.toml b/pyproject.toml index f5adb40b8..365ec16ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,7 @@ filterwarnings = [ "ignore:abi:DeprecationWarning", # from web3 "ignore::marshmallow.warnings.RemovedInMarshmallow4Warning", # from marshmallow ] +pythonpath = [".", "tests"] asyncio_mode = "auto" markers = [ "ArithmeticOperations", @@ -184,14 +185,8 @@ in_place = true remove_unused_variables = true remove_all_unused_imports = true -[tool.codespell] -ignore-words = '.codespellignore' -skip = '.git' -check-filenames = '' -check-hidden = '' - [tool.bandit] -exclude_dirs = ["tests"] +exclude_dirs = ["tests", "cairo_zero/tests"] [build-system] requires = ["hatchling"] diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/starknet.py b/tests/fixtures/starknet.py index a899fb347..a6989314a 100644 --- a/tests/fixtures/starknet.py +++ b/tests/fixtures/starknet.py @@ -38,7 +38,7 @@ def cairo_compile(path): - module_reader = get_module_reader(cairo_path=["src"]) + module_reader = get_module_reader(cairo_path=["cairo_zero"]) pass_manager = starknet_pass_manager( prime=DEFAULT_PRIME, diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 000000000..e69de29bb