diff --git a/.github/ISSUE_TEMPLATE/BUG-FORM.yml b/.github/ISSUE_TEMPLATE/BUG-FORM.yml index 8067de866..8972e9b91 100644 --- a/.github/ISSUE_TEMPLATE/BUG-FORM.yml +++ b/.github/ISSUE_TEMPLATE/BUG-FORM.yml @@ -14,11 +14,8 @@ body: description: What component is the bug in? multiple: true options: - - Forge - - Cast - - Anvil - - Foundryup - - Chisel + - zkForge + - zkCast - Other (please describe) validations: required: true diff --git a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml index d7df54030..aaba9fc3f 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml @@ -14,11 +14,8 @@ body: description: What component is the feature for? multiple: true options: - - Forge - - Cast - - Anvil - - Foundryup - - Chisel + - zkForge + - zkCast - Other (please describe) validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ef4a037ad..9edb5bc9c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,17 @@ - +# Why :hand: +* Reason why first thing was added to PR +* Reason why second thing was added to PR +* Reason why third thing was added to PR -## Motivation +# Evidence :camera: +Include screenshots, screen recordings, or `console` output here demonstrating that your changes work as intended - + -## Solution - - + diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml new file mode 100644 index 000000000..2c79d9b6a --- /dev/null +++ b/.github/workflows/check-pr-title.yml @@ -0,0 +1,18 @@ +name: Check PR title +on: + pull_request_target: + types: + - opened + - reopened + - edited + - synchronize + +jobs: + lint: + runs-on: ubuntu-latest + permissions: + statuses: write + steps: + - uses: aslafy-z/conventional-pr-title-action@v3 + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 277b1c943..79a421338 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -2,10 +2,10 @@ name: deny on: push: - branches: [master] + branches: [main] paths: [Cargo.lock, deny.toml] pull_request: - branches: [master] + branches: [main] paths: [Cargo.lock, deny.toml] env: diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml deleted file mode 100644 index dd2899dd3..000000000 --- a/.github/workflows/project.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: project - -on: - issues: - types: [opened, transferred] - -jobs: - add-to-project: - name: add issue - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/foundry-rs/projects/2 - github-token: ${{ secrets.GH_PROJECTS_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32c628819..b7985c472 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,10 +61,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release-docker: - name: Release Docker - uses: ./.github/workflows/docker-publish.yml - release: name: ${{ matrix.target }} (${{ matrix.os }}) runs-on: ${{ matrix.os }} @@ -238,8 +234,8 @@ jobs: issue: name: Open an issue runs-on: ubuntu-latest - needs: [prepare, release-docker, release, cleanup] - if: failure() + needs: [prepare, release, cleanup] + if: ${{ failure() }} steps: - uses: actions/checkout@v4 - uses: JasonEtco/create-an-issue@v2 diff --git a/.github/leaked_secrets.yaml b/.github/workflows/secret_scanner.yaml similarity index 85% rename from .github/leaked_secrets.yaml rename to .github/workflows/secret_scanner.yaml index 190c8190d..1d237c6f5 100644 --- a/.github/leaked_secrets.yaml +++ b/.github/workflows/secret_scanner.yaml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: TruffleHog OSS diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d3a3ef9f..02a9664f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ name: test on: push: branches: - - master + - main pull_request: concurrency: @@ -14,79 +14,24 @@ env: CARGO_TERM_COLOR: always jobs: - matrices: - name: build matrices - runs-on: ubuntu-latest - outputs: - test-matrix: ${{ steps.gen.outputs.test-matrix }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - name: Generate matrices - id: gen - env: - EVENT_NAME: ${{ github.event_name }} - run: | - output=$(python3 .github/scripts/matrices.py) - echo "::debug::test-matrix=$output" - echo "test-matrix=$output" >> $GITHUB_OUTPUT - - test: - name: test ${{ matrix.name }} - runs-on: ${{ matrix.os }} + doctests: + name: doc tests + runs-on: ubuntu-22.04-github-hosted-16core timeout-minutes: 60 - needs: matrices - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - target: ${{ matrix.target }} - - uses: taiki-e/install-action@nextest - # - uses: taiki-e/setup-cross-toolchain-action@v1 - # with: - # target: ${{ matrix.target }} - - name: Forge RPC cache - uses: actions/cache@v3 - with: - path: | - ~/.foundry/cache - ~/.config/.foundry/cache - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly - uses: Swatinem/rust-cache@v2 with: - # key: ${{ matrix.target }} - cache-on-failure: true - - name: Setup Git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - git config --global url."https://github.com/".insteadOf "git@github.com:" - - name: Test + cache-on-failure: true + - name: cargo test + run: cargo test --doc -p zkforge -p zkcast env: - SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} - run: cargo nextest run ${{ matrix.flags }} - - doctest: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo test --workspace --doc + RUST_TEST_THREADS: 2 clippy: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@clippy @@ -97,9 +42,10 @@ jobs: env: RUSTFLAGS: -Dwarnings - rustfmt: - runs-on: ubuntu-latest - timeout-minutes: 30 + fmt: + name: fmt + runs-on: ubuntu-22.04-github-hosted-16core + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly @@ -109,10 +55,10 @@ jobs: forge-fmt: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -124,12 +70,13 @@ jobs: shopt -s extglob cargo run --bin forge -- fmt --check testdata/**/!(Vm).sol - crate-checks: - runs-on: ubuntu-latest - timeout-minutes: 30 + feature-checks: + name: feature checks + runs-on: ubuntu-22.04-github-hosted-16core + timeout-minutes: 60 steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 with: diff --git a/README.md b/README.md index aeb88f341..ebc861ef5 100644 --- a/README.md +++ b/README.md @@ -1,693 +1,182 @@ # Foundry with zkSync Era v0.1 -This repository provides [Foundry](https://github.com/foundry-rs/foundry) functionality in Solidity for compiling, deploying, and interacting with smart contracts on zkSync Era. +This repository provides [Foundry](https://github.com/foundry-rs/foundry) functionality in Solidity for compiling, deploying, testing, and interacting with smart contracts on **zkSync Era**. -### Supported features +**What is foundry?** -- Compile smart contracts with the [zksolc compiler](https://github.com/matter-labs/zksolc-bin). -- Deploy smart contracts to zkSync Era mainnet, testnet, or local test node. -- Bridge assets L1 <-> L2. -- Call deployed contracts on zkSync Era testnet or local test node. -- Send transactions to deployed contracts on zkSync Era testnet or local test node. -- Simple 'PASS | FAIL' testing +Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. -### Known Issues +Foundry consists of: -- Currently users of `foundry-zksync` have to update import paths to be relative paths based on the complied source code location (e.g. `import "../lib/forge-std/src/console.sol";`) -- `script` command does not support `zksolc` currently -- Cheat codes are currently not supported +- **Forge:** Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast:** Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil:** Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel:** Fast, utilitarian, and verbose solidity REPL. -## Set up +Need help getting started with Foundry? Read the πŸ“– [Foundry Book](https://book.getfoundry.sh/) (WIP)! -### Prerequisites +Foundry-zkSync adds: -- [Rust compiler](https://www.rust-lang.org/tools/install). +- **zkForge:** zkSync testing framework (like Truffle, Hardhat and DappTools). +- **zkCast:** Swiss army knife for interacting with zkEVM smart contracts, sending transactions and getting chain data. -### Installation +Need help getting started with **Foundry-zkSync**? Read the πŸ“– [Usage Guides](./docs/dev/zksync/) (WIP)! -#### `zkforge` +## ⚠️ Caution -To install: +Please note that `foundry-zksync` is still in its **alpha** stage. Some features might not be fully supported yet and may not work as intended. However, it is open-sourced, and contributions are welcome! -``` -cargo install --path ./crates/zkforge --profile local --force --locked -``` - -#### `zkcast` +## πŸ“Š Features & Limitations -To install: - -``` -cargo install --path ./crates/zkcast --profile local --force --locked -``` - -## Usage - -### `zkcast` - -Spin up local Docker node: -- [Instructions to setup local-setup](https://era.zksync.io/docs/tools/testing/dockerized-testing.html) - -Interact with blockchain: - -#### Get chain id of local node - -```sh -zkcast chain-id --rpc-url http://localhost:3050 -``` +| βœ… Features | 🚫 Limitations | +|------------------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| Compile smart contracts with the [zksolc compiler](https://github.com/matter-labs/zksolc-bin). | Must use relative import paths based on compiled source code location. | +| Deploy smart contracts to zkSync Era mainnet, testnet, or local test node. | `script` command lacks `zksolc` support. | +| Bridge assets L1 <-> L2. | Cheat codes are not supported. | +| Call deployed contracts on zkSync Era testnet or local test node. | Lacks advanced testing methods (e.g., variant testing). | +| Send transactions to deployed contracts on zkSync Era testnet or local test node. | | +| Simple 'PASS / FAIL' testing. | | -**Output** +## πŸ“ Prerequisites -270 +- [Rust Compiler](https://www.rust-lang.org/tools/install) -#### Get chain id of testnet - -```sh -zkcast chain-id --rpc-url https://zksync2-testnet.zksync.dev:443 -``` - -**Output** - -```sh -280 -``` - -#### Get client - -```sh -zkcast client --rpc-url https://zksync2-testnet.zksync.dev:443 -``` - -**Output** - -```sh -zkSync/v2.0 -``` - -#### Get account's L2 ETH balance - -```sh -zkcast balance 0x42C7eF198f8aC9888E2B1b73e5B71f1D4535194A --rpc-url https://zksync2-testnet.zksync.dev:443 -``` - -**Output** - -```sh -447551277794355871 -``` - -#### Get gas price - -```sh -zkcast gas-price --rpc-url https://zksync2-testnet.zksync.dev:443 -``` - -**Example output** - -```sh -250000000 -``` - -#### Get timestamp of latest block - -```sh -zkcast age --block latest --rpc-url https://zksync2-testnet.zksync.dev:443 -``` - -**Example output** - -```sh -Mon May 1 16:11:07 2023 -``` - -#### Get latest block - -```sh -zkcast block latest --rpc-url https://zksync2-testnet.zksync.dev:443 -``` - -**Example output** - -```sh -baseFeePerGas 250000000 -difficulty 0 -extraData 0x -gasLimit 4294967295 -gasUsed 40277767 -hash 0x6c5b7c9b82b48bd77c0f506d74ed32aec6ab5c52e6c9c604ee8825a0b4a68289 -logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -miner 0x0000000000000000000000000000000000000000 -mixHash 0x0000000000000000000000000000000000000000000000000000000000000000 -nonce 0x0000000000000000 -number 5024177 -parentHash 0x9fbb3c9e5ef3b7807152367eeab5759cce14c290118de0e9011777a640cd7068 -receiptsRoot 0x0000000000000000000000000000000000000000000000000000000000000000 -sealFields [] -sha3Uncles 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 -size 0 -stateRoot 0x0000000000000000000000000000000000000000000000000000000000000000 -timestamp 1682957640 -totalDifficulty 0 -l1BatchNumber null -l1BatchTimestamp null -``` ---- - -### Compile with `zkforge zk-build` - -> Aliases: `zkforge zkbuild`, `zkforge zk-compile`, `zkforge zkb`. - -Compile smart contracts to zkEVM bytecode and store the compiled output files in a logical directory structure `/zkout/` for easy retrieval by other components of the application. - -```sh - -Compiler subcommands for zkSync - -Usage: -zkforge zk-build [OPTIONS] - -Options: - --use-zksolc Specify zksolc compiler version (default if left blank) - --is-system Enable the system contract compilation mode. - --force-evmla Sets the EVM legacy assembly pipeline forcibly - -h, --help Print help -``` +## πŸ’Ύ Installation -> `--is-system` flag: It is necessary to compile some contracts, including those that deploy other contracts (such as factory contracts), using the `--is-system` flag. These contracts should be placed in the `src/is-system/` folder. If the folder does not exist, manually create it. +Each tool within our suite can be installed individually, or you can install the entire suite at once. -![image](https://user-images.githubusercontent.com/76663878/236301037-2a536ab0-3d09-44f3-a74d-5f5891af335b.png) +### Installing `zkforge` πŸ› οΈ -### Example usage - -To compile with default compiler options (v1.3.11). - -```sh -zkforge zk-build -``` - -### Compiler settings - -Configure the `zksolc` compiler version using the optional `--use` flag. - -```bash -zkforge zkb --use 0.8.19 -``` - -**Example output** - -`zksolc` compiler artifacts can be found in the output folder: - -```bash -/zkout/ -``` -![image](https://user-images.githubusercontent.com/76663878/234152279-e144e489-41ab-4cbd-8321-8ccd9b0aa6ef.png) - -Example terminal output: - -![image](https://user-images.githubusercontent.com/76663878/236305625-8c7519e2-0c5e-492f-a4bc-3b019a95e34f.png) - -NOTE: Currently, until `forge remappings` are implemented, import paths must be relative to the contract importing it: - -![image](https://github.com/matter-labs/foundry-zksync/assets/76663878/490b34f4-e286-42a7-8570-d4b228ec10c7) - -`SimpleFactory.sol` and `AAFactory.sol` are in the `src/is-system/` folder. - ---- - -### Deploy with `zkforge zk-create` - -> Aliases: `zkforge zkcreate`, `zkforge zk-deploy`, `zkforge zkc` - -```sh -Deploy smart contracts to zksync. - -Usage: zkforge zk-create [OPTIONS] --rpc-url --chain --private-key - -Options: - -h, --help - Print help (see a summary with '-h') - -ZkCreate options: - --constructor-args ... - The constructor arguments. - - --constructor-args-path - The path to a file containing the constructor arguments. - - - The contract identifier in the form `:`. - -ZkSync Features: - --factory-deps ... - The factory dependencies in the form `:`. -``` - -#### Example - -To deploy `src/Greeter.sol` to zkSync testnet: - -```bash -zkforge zkc src/Greeter.sol:Greeter --constructor-args "ZkSync + Pineapple" --private-key <"PRIVATE_KEY"> --rpc-url https://zksync2-testnet.zksync.dev:443 --chain 280 -``` - -#### Output - -```txt -Deploying contract... -+-------------------------------------------------+ -Contract successfully deployed to address: 0x07d485ff2df314b240ec392ed86b137a661ddd35 -Transaction Hash: 0xdb6864fe1d19572a3ff509c5c7ed43f033d2dab8261a843808ed46e6e6ee51be -Gas used: 89879008 -Effective gas price: 250000000 -Block Number: 6651906 -+-------------------------------------------------+ -``` - ---- - -### Bridge assets L1 ↔ L2 with `zkcast zk-send` and `zkcast zk-deposit` - -### L1 β†’ L2 deposits - -```sh -zkcast zk-deposit --l1-rpc-url --l2-url --chain --private-key -``` -NOTE: Leave `` blank to bridge ETH +Run the following command: ```bash -Usage: zkcast zk-deposit --l1-rpc-url --l2-url [OPTIONS] [BRIDGE] [TIP] - -Arguments: - - The L2 address that receives the tokens. - - - Amount of token to deposit. - - [BRIDGE] - The address of a custom bridge to call. - - [TIP] - Optional fee that the user can choose to pay in addition to the regular transaction fee. - -Options: - -z, --l2-url - The zkSync RPC Layer 2 endpoint. Can be provided via the env var ZKSYNC_RPC_URL or --l2-url from the command line. - - NOTE: For Deposits, ETH_RPC_URL, or --rpc-url should be set to the Layer 1 RPC URL - - [env: ZKSYNC_RPC_URL=https://zksync2-testnet.zksync.dev] - - --token - Token to bridge. Leave blank for ETH. - - -h, --help - Print help (see a summary with '-h') -``` - -#### Example - error on this one - -```sh -zkcast zkdeposit 0x36615Cf349d7F6344891B1e7CA7C72883F5dc049 1000000 --rpc-url http://localhost:8545 --l2-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 -``` - -#### Output - -```txt -Bridging assets.... -Transaction Hash: 0x55793df0a636aedd098309e3487c6d9ec0910422d5b9f0bdbdf764bc82dc1b9f -``` ---- - -### L2 β†’ L1 withdrawals - -```sh -zkcast zk-send --withdraw --amount --rpc-url --private-key - - -Arguments: - [TO] The withdraw recipient. - - -Bridging options: - -w, --withdraw For L2 -> L1 withdrawals. - - --token Token to bridge. Leave blank for ETH. - - -a, --amount Amount of token to bridge. Required value when bridging -``` - -#### Example - -```sh -zkcast zk-send --withdraw 0x36615Cf349d7F6344891B1e7CA7C72883F5dc049 --amount 1000000 --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 -``` - -#### Output - -```text -Bridging assets.... -+-------------------------------------------------+ -Transaction Hash: 0x3562f47db61de149fb7266c3a65935c4e8324cceb5a1db8718390a8a5a210191 -Gas used: 10276475 -Effective gas price: 250000000 -Block Number: 6652714 -+-------------------------------------------------+ +cargo install --path ./crates/zkforge --profile local --force --locked ``` ---- - -## Interact with contract with `zkcast zk-send` +This installs `zkforge` to `~/.cargo/bin`, making it available as an executable. -> Aliases: `zkcast zks`, `zkcast zksend` +### Installing `zkcast` πŸ“‘ -Interact with deployed contracts in the native foundry/zkforge fashion using the CLI `zkcast zk-send` command. - -```sh -Sign and publish a zksync transaction. - -Usage: zkcast zk-send [OPTIONS] [TO] [SIG] [ARGS]... - -Arguments: - [TO] The destination of the transaction. - - [SIG] The signature of the function to call. - - [ARGS]... The arguments of the function to call. - -Options: - -h, --help Print help (see a summary with '-h') - -Bridging options: - -d, --deposit For L1 -> L2 deposits. - - -w, --withdraw For L2 -> L1 withdrawals. - - --token Token to bridge. Leave blank for ETH. - - -a, --amount Amount of token to bridge. Required value when bridging -``` - -- Retrieve and interact with chain data. For example, block numbers and gas estimates. -- Interact with deployed contracts on (zkSync Era testnet or local Docker node). - -### Non-state changing calls - -```sh -zkcast call --rpc-url -``` - -#### Example +Run the following command: ```bash -zkcast call 0x97b985951fd3e0c1d996421cc783d46c12d00082 "greet()(string)" --rpc-url http://localhost:3050 -``` - -#### Output - -```txt -ZkSync + Pineapple -``` - -### Send transactions - -```sh -zkcast zk-send --rpc-url --private-key --chain -``` - -#### Example - -```sh -zkcast zk-send 0x97b985951fd3e0c1d996421cc783d46c12d00082 "setGreeting(string)" "Killer combo!" --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 -``` - -#### Output - -```txt -Sending transaction.... -Transaction Hash: 0x7651fba8ddeb624cca93f89da493675ccbc5c6d36ee25ed620b07424ce338552 -``` - -#### Verify output - -```sh -zkcast call 0x97b985951fd3e0c1d996421cc783d46c12d00082 "greet()(string)" --rpc-url http://localhost:3050 -``` - -#### Output - -```txt -Killer combo! +cargo install --path ./crates/zkcast --profile local --force --locked ``` ---- +This installs `zkcast` to `~/.cargo/bin`, allowing it to be used as an executable. -## Deploy and interact with `SimpleFactory.sol` +### Installing the Entire Suite πŸ“¦ -### Compile contract - -`SimpleFactory.sol` must be compiled with the `is-system` flag, so they need to be placed in the `src/is-system/` folder +To install all the tools in the suite: ```bash -zkforge zk-build +cargo build --release ``` -### Deploy `SimpleFactory.sol` - -```sh -zkforge zkc src/SimpleFactory.sol:SimpleFactory --constructor-args 01000041691510d85ddfc6047cba6643748dc028636d276f09a546ab330697ef 010000238a587670be26087b7812eab86eca61e7c4014522bdceda86adb2e82f --factory-deps src/Child.sol:Child src/StepChild.sol:StepChild --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --rpc-url http://localhost:3050 --chain 270 -``` +## Quickstart -#### Output - -```txt -Deploying contract... -+-------------------------------------------------+ -Contract successfully deployed to address: 0xa1b809005e589f81de6ef9f48d67e35606c05fc3 -Transaction Hash: 0x34782985ba7c70b6bc4a8eb2b95787baec29356171fdbb18608037a2fcd7eda8 -Gas used: 168141 -Effective gas price: 250000000 -Block Number: 249 -+-------------------------------------------------+ +Run: +``` +zkforge init --template https://github.com/dutterbutter/hello-foundry-zksync ``` -### Deploy `StepChild.sol` via `SimpleFactory.sol` +Let's check out what zkforge generated for us: -```sh -zkcast zk-send 0x23cee3fb585b1e5092b7cfb222e8e873b05e9519 "newStepChild()" --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 ``` - -#### Output - -```sh -Sending transaction.... -Transaction Hash: 0xa82a0636b71af058d4916d81868eebc41173ca07b78d30fe57f4b74e9294ef25 +$ cd hello-foundry-zksync +$ tree . -d -L 1 +. +β”œβ”€β”€ abis +β”œβ”€β”€ broadcast +β”œβ”€β”€ interfaces +β”œβ”€β”€ lib +β”œβ”€β”€ script +β”œβ”€β”€ src +β”œβ”€β”€ test ``` -### Interact with `SimpleFactory.sol` - -```sh -../foundry-zksync/target/debug/zkcast call 0x23cee3fb585b1e5092b7cfb222e8e873b05e9519 "stepChildren(uint256)(address)" 0 --rpc-url http://localhost:3050 -``` +Due to a known issue where import paths need to be relative, it's necessary to modify certain paths in the `hello-foundry-zksync` project before compiling. This ensures proper resolution of dependencies during compilation. The required changes are outlined below: -#### Output +**Modifications:** +1. In the file `lib/forge-std/src/StdAssertions.sol`, adjust the import statement as follows: + ```solidity + import {DSTest} from "../lib/ds-test/src/test.sol"; + ``` -`StepChild.sol` deployed address: +2. In the file `lib/forge-std/src/Test.sol`, update the import path in a similar manner: + ```solidity + import {DSTest} from "../lib/ds-test/src/test.sol"; + ``` -```txt -0xbc88C5Cdfe2659ebDD5dbb7e1a695A4cb189Df96 +We can then build the project with zkforge zkbuild: ``` - -### Interact with `StepChild.sol` - -Use `zkcast call` to check initial state: - -```sh -zkcast call 0xbc88C5Cdfe2659ebDD5dbb7e1a695A4cb189Df96 "isEnabled()(bool)" --rpc-url http://localhost:3050 -``` - -#### Output: - -```txt -false -``` - -Use `zkcast zk-send` to modify state: - -```sh -zkcast zk-send 0xbc88C5Cdfe2659ebDD5dbb7e1a695A4cb189Df96 "enable()" --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 -``` - -#### Output - -```sh -Sending transaction.... -Transaction Hash: 0xe005e15e9f58b7dcdcc7b16a9d5c706ddef7a4c9cab82216ea944d5344ba01ae -``` - -Use `zkcast call` to check modified state. - -```sh -zkcast call 0xbc88C5Cdfe2659ebDD5dbb7e1a695A4cb189Df96 "isEnabled()(bool)" --rpc-url http://localhost:3050 -``` - -#### Output - -```txt -true -``` - ---- - -## Account abstraction multisig - -This section compiles, deploys, and interacts with the contracts from the zkSync Era [**Account Abstraction Multisig example**](https://era.zksync.io/docs/dev/tutorials/custom-aa-tutorial.html) - -Contracts: - -- [**AAFactory.sol**](https://era.zksync.io/docs/dev/tutorials/custom-aa-tutorial.html) -- [**TwoUserMultiSig.sol**](https://github.com/sammyshakes/sample-fzksync-project/blob/main/src/TwoUserMultiSig.sol) - -### Compile `AAFactory.sol` - -`AAFactory.sol` needs to be compiled with the `--is-system` flag because it will be interacting with system contracts to deploy the multisig wallets. - -Place the contract in the `src/is-system/` folder - -```sh -# command line using zkforge zk-build -../foundry-zksync/target/debug/zkforge zk-build -``` - -#### Output - -```sh -AAFactory -> Bytecode Hash: "010000791703a54dbe2502b00ee470989c267d0f6c0d12a9009a947715683744" +$ zkforge zkbuild +Compiling smart contracts... +Child -> Bytecode Hash: 010000410c1f3728a3887d9bc854d978ce441ccef394319cb26c58e0ba90df46 +Counter -> Bytecode Hash: 0100003bc44686be52940f3f2bd8a0feef17700663cba9edb978886c08123811 +Greeter -> Bytecode Hash: 0100008f03cbc9c98bb0a883736bf9c1d8801b74928ed78148ddbd5445defddf +StepChild -> Bytecode Hash: 010000239f712c49b5804a34b1f995e034d853e2c6d2edcb60646f1bf9f057f2 +Compiler run completed with warnings +TwoUserMultisig -> Bytecode Hash: 01000757a0867b6d7aba75853f126e7780bd893ae384a4718a2a03a6b53a5ee1 +AAFactory -> Bytecode Hash: 0100007b76ee1ed575d19043b0b995632ac07ae996aefbbc8238f490f492c793 +SimpleFactory -> Bytecode Hash: 0100021b7653e052f7f8218197d79e28de792ff243a30711fb63251644d47524 Compiled Successfully ``` -### Deploy `AAFactory.sol`: - -To deploy the factory we need the `Bytecode Hash` of the `TwoUserMultiSig.sol` contract to provide to the constructor of `AAFactory.sol`. - -```js -constructor(bytes32 _aaBytecodeHash) { - aaBytecodeHash = _aaBytecodeHash; - } -``` - -Note: `aaBytecodeHash` = Bytecode hash of `TwoUserMultiSig.sol` +#### Running Tests -To deploy a contract that deploys other contracts, it is necessary to provide the bytecodes of the child contracts in the `factory-deps` field of the transaction. This can be accomplished by using the `--factory-deps` flag and providing the full contract path in the format: `:` +You can run the tests using `zkforge test`. The command and its expected output are shown below: -```sh -# command line using zkforge zk-create -../foundry-zksync/target/debug/zkforge zkc src/is-system/AAFactory.sol:AAFactory --constructor-args 010007572230f4df5b4e855ff48d4cdfffc9405522117d7e020ee42650223460 --factory-deps src/TwoUserMultiSig.sol:TwoUserMultisig --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --rpc-url http://localhost:3050 --chain 270 -``` - -#### Output - -```sh -Deploying contract... -+-------------------------------------------------+ -Contract successfully deployed to address: 0xd5608cec132ed4875d19f8d815ec2ac58498b4e5 -Transaction Hash: 0x0e6f55ff1619af8b3277853a8f2941d0481635880358316f03ae264e2de059ed -Gas used: 154379 -Effective gas price: 250000000 -Block Number: 291 -+-------------------------------------------------+ -``` - -Now that we have the `AAFactory.sol` contract address we can call `deployAccount` function to deploy a new `TwoUserMultiSig.sol` instance. - -Here is the interface of `deployAccount`. - -```js -function deployAccount(bytes32 salt, address owner1, address owner2) external returns (address accountAddress) -``` +```bash +$ zkforge test -We need to provide the two owner addresses for the newly deployed multisig: +Running 2 tests for Counter.sol:ContractBTest +[PASS] test_CannotSubtract43() (gas: 9223372034707517612) +[PASS] test_NumberIs42() (gas: 9223372034707517612) +Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 43.08ms -```js -owner1 = 0xa61464658AfeAf65CccaaFD3a512b69A83B77618 -owner2 = 0x0D43eB5B8a47bA8900d84AA36656c92024e9772e -``` +Running 1 test for Counter.sol:OwnerUpOnlyTest +[PASS] test_IncrementAsOwner() (gas: 9223372034707517612) +Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 43.46ms -We are also just using a `0x00` value for the ***salt*** parameter. (You will need a unique value for salt for each instance that uses same owner wallets). +Running 2 tests for Counter.sol:CounterTest +[PASS] test_Increment() (gas: 9223372034707517612) +[PASS] test_Increment_twice() (gas: 9223372034707517612) +Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 47.81ms -```sh -# command line using zkcast zk-send -../foundry-zksync/target/debug/zkcast zk-send 0xd5608cec132ed4875d19f8d815ec2ac58498b4e5 "deployAccount(bytes32,address,address)(address)" 0x00 0xa61464658AfeAf65CccaaFD3a512b69A83B77618 0x0D43eB5B8a47bA8900d84AA36656c92024e9772e --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 +Ran 3 test suites: 5 tests passed, 0 failed, 0 skipped (5 total tests) ``` -#### Output +## Configuration -```sh -Sending transaction.... -Transaction Hash: 0x43a4dded84a12891dfae4124b42b9f091750e953193bd779a7e5e4d422909e73 -0x03e50ec034f1d363de0add752c33d4831a2731bf, <---- Deployed contract address -``` +### Using `foundry.toml` -The new `TwoUserMultiSig.sol` contract has been deployed to: +Foundry is designed to be very configurable. You can configure Foundry using a file called [`foundry.toml`](./crates/config) in the root of your project, or any other parent directory. See [config package](./crates/config/README.md#all-options) for all available options. -```txt -0x03e50ec034f1d363de0add752c33d4831a2731bf -``` +Configuration can be arbitrarily namespaced by profiles. The default profile is named `default` (see ["Default Profile"](./crates/config/README.md#default-profile)). -Check the tx receipt using `zkcast tx ` +You can select another profile using the `FOUNDRY_PROFILE` environment variable. You can also override parts of your configuration using `FOUNDRY_` or `DAPP_` prefixed environment variables, like `FOUNDRY_SRC`. -```sh -../foundry-zksync/target/debug/zkcast tx 0x22364a3e191ad10013c5f20036e9696e743a4f686bc58a0106ef0b9e7592347c --rpc-url http://localhost:3050 -``` - -#### Output - -```sh -blockHash 0x2f3e2be46a7cb9f9e9df503903990e6670e88224e52232c988b5a730c82d98c0 -blockNumber 297 -from 0x36615Cf349d7F6344891B1e7CA7C72883F5dc049 -gas 217367 -gasPrice 250000000 -hash 0x43a4dded84a12891dfae4124b42b9f091750e953193bd779a7e5e4d422909e73 -input 0x76fb8b650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a61464658afeaf65cccaafd3a512b69a83b776180000000000000000000000000d43eb5b8a47ba8900d84aa36656c92024e9772e -nonce 147 -r 0x16385d99ccaaa5e84bb97d76a0afb310350c2ca4165ed41d458efa80cd76d3bd -s 0x3ec55287f223e760b7dd82a676feece939832e4c5a3d73f3aa979bd2cd48801c -to 0xd5608cEC132ED4875D19f8d815EC2ac58498B4E5 -transactionIndex 0 -v 1 -value 0 -l1BatchNumber 149 -l1BatchTxIndex 0 -``` +`zkforge init` creates a basic, extendable `foundry.toml` file. -Verify with `zkcast call` to call the public variables 'owner1' and 'owner2' on the newly deployed `TwoUserMultiSig.sol` contract. +To see your current configuration, run `zkforge config`. To see only basic options (as set with `zkforge init`), run `zkforge config --basic`. This can be used to create a new `foundry.toml` file with `zkforge config --basic > foundry.toml`. -Verify `owner1`: +By default `zkforge config` shows the currently selected foundry profile and its values. It also accepts the same arguments as `zkforge build`. -```sh -# command line using zkcast call -../foundry-zksync/target/debug/zkcast call 0x03e50ec034f1d363de0add752c33d4831a2731bf "owner1()(address)" --rpc-url http://localhost:3050 -``` +### DappTools Compatibility -#### Output +You can re-use your `.dapprc` environment variables by running `source .dapprc` before using a Foundry tool. -```txt -0xa61464658AfeAf65CccaaFD3a512b69A83B77618 -``` +### Additional Configuration -Verify `owner2`: +You can find additional setup and configurations guides in the [Foundry Book][foundry-book]: -```sh -# command line using zkcast call -../foundry-zksync/target/debug/zkcast call 0x03e50ec034f1d363de0add752c33d4831a2731bf "owner2()(address)" --rpc-url http://localhost:3050 -``` +- [Setting up VSCode][vscode-setup] +- [Shell autocompletions][shell-setup] -#### Output +## Contributing -```txt -0x0D43eB5B8a47bA8900d84AA36656c92024e9772e -``` +See our [contributing guidelines](./CONTRIBUTING.md). ## Troubleshooting @@ -722,4 +211,18 @@ This means that our zksync compiler doesn't support that version of solidity yet In such case, please remove the artifacts (by removing `zkout` directory) and re-run with the older version of solidity (`--use 0.8.19`) for example. -You might also have to remove the `~/.svm/0.8.20/solc-0.8.20` file. \ No newline at end of file +You might also have to remove the `~/.svm/0.8.20/solc-0.8.20` file. + +## Acknowledgements + +- Foundry is a clean-room rewrite of the testing framework [DappTools](https://github.com/dapphub/dapptools). None of this would have been possible without the DappHub team's work over the years. +- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc](https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. +- [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. +- [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. +- All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs) & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. + +[foundry-book]: https://book.getfoundry.sh +[foundry-gha]: https://github.com/foundry-rs/foundry-toolchain +[ethers-solc]: https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/ +[vscode-setup]: https://book.getfoundry.sh/config/vscode.html +[shell-setup]: https://book.getfoundry.sh/config/shell-autocompletion.html \ No newline at end of file diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 5696937c2..bb5f93251 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -217,6 +217,7 @@ impl CallArgs { } /// fills the builder from create arg +#[allow(clippy::needless_pass_by_ref_mut)] async fn fill_create( builder: &mut TxBuilder<'_, Provider>, value: Option, @@ -239,6 +240,7 @@ async fn fill_create( } /// fills the builder from args +#[allow(clippy::needless_pass_by_ref_mut)] async fn fill_tx( builder: &mut TxBuilder<'_, Provider>, value: Option, diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 25b224182..47b1c028c 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -347,7 +347,7 @@ fn create_fork_at_transaction( /// Creates the request object for a new fork request fn create_fork_request( - ccx: &mut CheatsCtxt, + ccx: &CheatsCtxt, url_or_alias: &str, block: Option, ) -> Result { diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 5c95cfc2f..4fc877eb7 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -255,6 +255,7 @@ impl Cheatcodes { /// /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's /// revert would run into issues. + //#[allow(private_bounds)] // TODO pub fn on_revert(&mut self, data: &mut EVMData<'_, DB>) { trace!(deals = ?self.eth_deals.len(), "rolling back deals"); diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index 376f89ab5..65474a139 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -227,6 +227,7 @@ impl Provider for ChiselParser { } /// Evaluate a single Solidity line. +#[allow(clippy::needless_pass_by_ref_mut)] async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) { match dispatcher.dispatch(line).await { DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => if let Some(msg) = msg { @@ -245,6 +246,7 @@ async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) { /// Evaluate multiple Solidity source files contained within a /// Chisel prelude directory. +#[allow(clippy::needless_pass_by_ref_mut)] async fn evaluate_prelude( dispatcher: &mut ChiselDispatcher, maybe_prelude: Option, @@ -270,6 +272,7 @@ async fn evaluate_prelude( } /// Loads a single Solidity file into the prelude. +#[allow(clippy::needless_pass_by_ref_mut)] async fn load_prelude_file(dispatcher: &mut ChiselDispatcher, file: PathBuf) -> eyre::Result<()> { let prelude = fs::read_to_string(file) .wrap_err("Could not load source file. Are you sure this path is correct?")?; diff --git a/crates/chisel/src/lib.rs b/crates/chisel/src/lib.rs index 2004ed0c4..9bbff7575 100644 --- a/crates/chisel/src/lib.rs +++ b/crates/chisel/src/lib.rs @@ -3,7 +3,7 @@ #![warn(unused_extern_crates)] #![forbid(unsafe_code)] #![forbid(where_clauses_object_safety)] - +#![allow(unused_imports)] /// REPL input dispatcher module pub mod dispatcher; diff --git a/crates/common/src/zk_utils.rs b/crates/common/src/zk_utils.rs index b71a00705..5b9664044 100644 --- a/crates/common/src/zk_utils.rs +++ b/crates/common/src/zk_utils.rs @@ -133,7 +133,7 @@ pub fn get_chain(chain: Option) -> Result { /// # Examples /// /// ``` -/// use foundry_cli::cmd::cast::zk_utils::decode_hex; +/// use foundry_common::zk_utils::decode_hex; /// let hex_string = "48656c6c6f2c20576f726c6421"; /// let bytes = decode_hex(hex_string).expect("Error decoding hex"); /// assert_eq!(bytes, vec![72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]); diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 85559d998..ae107b425 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -92,7 +92,7 @@ impl Executor { pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> { trace!("deploying local create2 deployer"); - return Ok(()) + Ok(()) } /// Set the balance of an account. diff --git a/crates/evm/src/executor/inspector/cheatcodes/expect.rs b/crates/evm/src/executor/inspector/cheatcodes/expect.rs new file mode 100644 index 000000000..a14344a92 --- /dev/null +++ b/crates/evm/src/executor/inspector/cheatcodes/expect.rs @@ -0,0 +1,572 @@ +use super::{bail, ensure, fmt_err, Cheatcodes, Result}; +use crate::{abi::HEVMCalls, executor::backend::DatabaseExt}; +use alloy_dyn_abi::DynSolType; +use alloy_primitives::{Address, Bytes, Log as RawLog, U256}; +use foundry_utils::{ + error::{ERROR_PREFIX, REVERT_PREFIX}, + types::ToAlloy, +}; +use once_cell::sync::Lazy; +use revm::{ + interpreter::{return_ok, InstructionResult}, + primitives::Bytecode, + EVMData, +}; +use std::cmp::Ordering; + +/// For some cheatcodes we may internally change the status of the call, i.e. in `expectRevert`. +/// Solidity will see a successful call and attempt to decode the return data. Therefore, we need +/// to populate the return with dummy bytes so the decode doesn't fail. +/// +/// 8912 bytes was arbitrarily chosen because it is long enough for return values up to 256 words in +/// size. +static DUMMY_CALL_OUTPUT: Lazy = Lazy::new(|| Bytes::from_static(&[0u8; 8192])); + +/// Same reasoning as [DUMMY_CALL_OUTPUT], but for creates. +static DUMMY_CREATE_ADDRESS: Address = + Address::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + +#[derive(Clone, Debug, Default)] +pub struct ExpectedRevert { + /// The expected data returned by the revert, None being any + pub reason: Option, + /// The depth at which the revert is expected + pub depth: u64, +} + +fn expect_revert(state: &mut Cheatcodes, reason: Option, depth: u64) -> Result { + ensure!( + state.expected_revert.is_none(), + "You must call another function prior to expecting a second revert." + ); + state.expected_revert = Some(ExpectedRevert { reason, depth }); + Ok(Bytes::new()) +} + +#[instrument(skip_all, fields(expected_revert, status, retdata = hex::encode(&retdata)))] +pub fn handle_expect_revert( + is_create: bool, + expected_revert: Option<&Bytes>, + status: InstructionResult, + retdata: Bytes, +) -> Result<(Option
, Bytes)> { + trace!("handle expect revert"); + + ensure!(!matches!(status, return_ok!()), "Call did not revert as expected"); + + macro_rules! success_return { + () => { + Ok(if is_create { + (Some(DUMMY_CREATE_ADDRESS), Bytes::new()) + } else { + trace!("successfully handled expected revert"); + (None, DUMMY_CALL_OUTPUT.clone()) + }) + }; + } + + // If None, accept any revert + let mut expected_revert = match expected_revert { + Some(x) => x.clone(), + None => return success_return!(), + }; + + if !expected_revert.is_empty() && retdata.is_empty() { + bail!("Call reverted as expected, but without data"); + } + + let mut actual_revert = retdata; + if actual_revert.len() >= 4 && + matches!(actual_revert[..4].try_into(), Ok(ERROR_PREFIX | REVERT_PREFIX)) + { + if let Ok(parsed_bytes) = DynSolType::Bytes.abi_decode(&actual_revert[4..]) { + if let Some(bytes) = parsed_bytes.as_bytes().map(|b| b.to_vec()) { + actual_revert = bytes.into(); + } + } + } + + if actual_revert == *expected_revert { + success_return!() + } else { + let stringify = |data: &mut Bytes| { + DynSolType::String + .abi_decode(data.0.as_ref()) + .ok() + .and_then(|d| d.as_str().map(|s| s.to_owned())) + .or_else(|| std::str::from_utf8(data.as_ref()).ok().map(ToOwned::to_owned)) + .unwrap_or_else(|| hex::encode_prefixed(data)) + }; + Err(fmt_err!( + "Error != expected error: {} != {}", + stringify(&mut actual_revert), + stringify(&mut expected_revert), + )) + } +} + +#[derive(Clone, Debug, Default)] +pub struct ExpectedEmit { + /// The depth at which we expect this emit to have occurred + pub depth: u64, + /// The log we expect + pub log: Option, + /// The checks to perform: + /// + /// β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β” + /// β”‚topic 1β”‚topic 2β”‚topic 3β”‚dataβ”‚ + /// β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”˜ + pub checks: [bool; 4], + /// If present, check originating address against this + pub address: Option
, + /// Whether the log was actually found in the subcalls + pub found: bool, +} + +pub fn handle_expect_emit(state: &mut Cheatcodes, log: RawLog, address: &Address) { + // Fill or check the expected emits. + // We expect for emit checks to be filled as they're declared (from oldest to newest), + // so we fill them and push them to the back of the queue. + // If the user has properly filled all the emits, they'll end up in their original order. + // If not, the queue will not be in the order the events will be intended to be filled, + // and we'll be able to later detect this and bail. + + // First, we can return early if all events have been matched. + // This allows a contract to arbitrarily emit more events than expected (additive behavior), + // as long as all the previous events were matched in the order they were expected to be. + if state.expected_emits.iter().all(|expected| expected.found) { + return + } + + // if there's anything to fill, we need to pop back. + let event_to_fill_or_check = + if state.expected_emits.iter().any(|expected| expected.log.is_none()) { + state.expected_emits.pop_back() + // Else, if there are any events that are unmatched, we try to match to match them + // in the order declared, so we start popping from the front (like a queue). + } else { + state.expected_emits.pop_front() + }; + + let mut event_to_fill_or_check = + event_to_fill_or_check.expect("We should have an emit to fill or check. This is a bug"); + + match event_to_fill_or_check.log { + Some(ref expected) => { + let expected_topic_0 = expected.topics().first(); + let log_topic_0 = log.topics().first(); + + // same topic0 and equal number of topics should be verified further, others are a no + // match + if expected_topic_0 + .zip(log_topic_0) + .map_or(false, |(a, b)| a == b && expected.topics().len() == log.topics().len()) + { + // Match topics + event_to_fill_or_check.found = log + .topics() + .iter() + .skip(1) + .enumerate() + .filter(|(i, _)| event_to_fill_or_check.checks[*i]) + .all(|(i, topic)| topic == &expected.topics()[i + 1]); + + // Maybe match source address + if let Some(addr) = event_to_fill_or_check.address { + event_to_fill_or_check.found &= addr == *address; + } + + // Maybe match data + if event_to_fill_or_check.checks[3] { + event_to_fill_or_check.found &= expected.data == log.data; + } + } + + // If we found the event, we can push it to the back of the queue + // and begin expecting the next event. + if event_to_fill_or_check.found { + state.expected_emits.push_back(event_to_fill_or_check); + } else { + // We did not match this event, so we need to keep waiting for the right one to + // appear. + state.expected_emits.push_front(event_to_fill_or_check); + } + } + // Fill the event. + None => { + event_to_fill_or_check.log = Some(log); + state.expected_emits.push_back(event_to_fill_or_check); + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct ExpectedCallData { + /// The expected value sent in the call + pub value: Option, + /// The expected gas supplied to the call + pub gas: Option, + /// The expected *minimum* gas supplied to the call + pub min_gas: Option, + /// The number of times the call is expected to be made. + /// If the type of call is `NonCount`, this is the lower bound for the number of calls + /// that must be seen. + /// If the type of call is `Count`, this is the exact number of calls that must be seen. + pub count: u64, + /// The type of call + pub call_type: ExpectedCallType, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum ExpectedCallType { + #[default] + Count, + NonCount, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct MockCallDataContext { + /// The partial calldata to match for mock + pub calldata: Bytes, + /// The value to match for mock + pub value: Option, +} + +#[derive(Clone, Debug)] +pub struct MockCallReturnData { + /// The return type for the mocked call + pub ret_type: InstructionResult, + /// Return data or error + pub data: Bytes, +} + +impl Ord for MockCallDataContext { + fn cmp(&self, other: &Self) -> Ordering { + // Calldata matching is reversed to ensure that a tighter match is + // returned if an exact match is not found. In case, there is + // a partial match to calldata that is more specific than + // a match to a msg.value, then the more specific calldata takes + // precedence. + self.calldata.cmp(&other.calldata).reverse().then(self.value.cmp(&other.value).reverse()) + } +} + +impl PartialOrd for MockCallDataContext { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) -> Result { + ensure!(start < end, "Invalid memory range: [{start}:{end}]"); + #[allow(clippy::single_range_in_vec_init)] + let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]); + offsets.push(start..end); + Ok(Bytes::new()) +} + +/// Handles expected calls specified by the `vm.expectCall` cheatcode. +/// +/// It can handle calls in two ways: +/// - If the cheatcode was used with a `count` argument, it will expect the call to be made exactly +/// `count` times. +/// e.g. `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f), 4)` will expect the +/// call to address(0xc4f3) with selector `0xd34db33f` to be made exactly 4 times. If the amount of +/// calls is less or more than 4, the test will fail. Note that the `count` argument cannot be +/// overwritten with another `vm.expectCall`. If this is attempted, `expectCall` will revert. +/// - If the cheatcode was used without a `count` argument, it will expect the call to be made at +/// least the amount of times the cheatcode +/// was called. This means that `vm.expectCall` without a count argument can be called many times, +/// but cannot be called with a `count` argument after it was called without one. If the latter +/// happens, `expectCall` will revert. e.g `vm.expectCall(address(0xc4f3), +/// abi.encodeWithSelector(0xd34db33f))` will expect the call to address(0xc4f3) and selector +/// `0xd34db33f` to be made at least once. If the amount of calls is 0, the test will fail. If the +/// call is made more than once, the test will pass. +#[allow(clippy::too_many_arguments)] +fn expect_call( + state: &mut Cheatcodes, + target: Address, + calldata: Vec, + value: Option, + gas: Option, + min_gas: Option, + count: u64, + call_type: ExpectedCallType, +) -> Result { + match call_type { + ExpectedCallType::Count => { + // Get the expected calls for this target. + let expecteds = state.expected_calls.entry(target).or_default(); + // In this case, as we're using counted expectCalls, we should not be able to set them + // more than once. + ensure!( + !expecteds.contains_key(&calldata), + "Counted expected calls can only bet set once." + ); + expecteds + .insert(calldata, (ExpectedCallData { value, gas, min_gas, count, call_type }, 0)); + Ok(Bytes::new()) + } + ExpectedCallType::NonCount => { + let expecteds = state.expected_calls.entry(target).or_default(); + // Check if the expected calldata exists. + // If it does, increment the count by one as we expect to see it one more time. + if let Some(expected) = expecteds.get_mut(&calldata) { + // Ensure we're not overwriting a counted expectCall. + ensure!( + expected.0.call_type == ExpectedCallType::NonCount, + "Cannot overwrite a counted expectCall with a non-counted expectCall." + ); + expected.0.count += 1; + } else { + // If it does not exist, then create it. + expecteds.insert( + calldata, + (ExpectedCallData { value, gas, min_gas, count, call_type }, 0), + ); + } + Ok(Bytes::new()) + } + } +} + +#[instrument(level = "error", name = "expect", target = "evm::cheatcodes", skip_all)] +pub fn apply( + state: &mut Cheatcodes, + data: &mut EVMData<'_, DB>, + call: &HEVMCalls, +) -> Option { + let result = match call { + HEVMCalls::ExpectRevert0(_) => expect_revert(state, None, data.journaled_state.depth()), + HEVMCalls::ExpectRevert1(inner) => { + expect_revert(state, Some(inner.0.clone().0.into()), data.journaled_state.depth()) + } + HEVMCalls::ExpectRevert2(inner) => { + expect_revert(state, Some(inner.0.into()), data.journaled_state.depth()) + } + HEVMCalls::ExpectEmit0(_) => { + state.expected_emits.push_back(ExpectedEmit { + depth: data.journaled_state.depth(), + checks: [true, true, true, true], + ..Default::default() + }); + Ok(Bytes::new()) + } + HEVMCalls::ExpectEmit1(inner) => { + state.expected_emits.push_back(ExpectedEmit { + depth: data.journaled_state.depth(), + checks: [true, true, true, true], + address: Some(inner.0.to_alloy()), + ..Default::default() + }); + Ok(Bytes::new()) + } + HEVMCalls::ExpectEmit2(inner) => { + state.expected_emits.push_back(ExpectedEmit { + depth: data.journaled_state.depth(), + checks: [inner.0, inner.1, inner.2, inner.3], + ..Default::default() + }); + Ok(Bytes::new()) + } + HEVMCalls::ExpectEmit3(inner) => { + state.expected_emits.push_back(ExpectedEmit { + depth: data.journaled_state.depth(), + checks: [inner.0, inner.1, inner.2, inner.3], + address: Some(inner.4.to_alloy()), + ..Default::default() + }); + Ok(Bytes::new()) + } + HEVMCalls::ExpectCall0(inner) => expect_call( + state, + inner.0.to_alloy(), + inner.1.to_vec(), + None, + None, + None, + 1, + ExpectedCallType::NonCount, + ), + HEVMCalls::ExpectCall1(inner) => expect_call( + state, + inner.0.to_alloy(), + inner.1.to_vec(), + None, + None, + None, + inner.2, + ExpectedCallType::Count, + ), + HEVMCalls::ExpectCall2(inner) => expect_call( + state, + inner.0.to_alloy(), + inner.2.to_vec(), + Some(inner.1.to_alloy()), + None, + None, + 1, + ExpectedCallType::NonCount, + ), + HEVMCalls::ExpectCall3(inner) => expect_call( + state, + inner.0.to_alloy(), + inner.2.to_vec(), + Some(inner.1.to_alloy()), + None, + None, + inner.3, + ExpectedCallType::Count, + ), + HEVMCalls::ExpectCall4(inner) => { + let value = inner.1.to_alloy(); + // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas + // to ensure that the basic fallback function can be called. + let positive_value_cost_stipend = if value > U256::ZERO { 2300 } else { 0 }; + + expect_call( + state, + inner.0.to_alloy(), + inner.3.to_vec(), + Some(value), + Some(inner.2 + positive_value_cost_stipend), + None, + 1, + ExpectedCallType::NonCount, + ) + } + HEVMCalls::ExpectCall5(inner) => { + let value = inner.1.to_alloy(); + // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas + // to ensure that the basic fallback function can be called. + let positive_value_cost_stipend = if value > U256::ZERO { 2300 } else { 0 }; + + expect_call( + state, + inner.0.to_alloy(), + inner.3.to_vec(), + Some(value), + Some(inner.2 + positive_value_cost_stipend), + None, + inner.4, + ExpectedCallType::Count, + ) + } + HEVMCalls::ExpectCallMinGas0(inner) => { + let value = inner.1.to_alloy(); + // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas + // to ensure that the basic fallback function can be called. + let positive_value_cost_stipend = if value > U256::ZERO { 2300 } else { 0 }; + + expect_call( + state, + inner.0.to_alloy(), + inner.3.to_vec(), + Some(value), + None, + Some(inner.2 + positive_value_cost_stipend), + 1, + ExpectedCallType::NonCount, + ) + } + HEVMCalls::ExpectCallMinGas1(inner) => { + let value = inner.1.to_alloy(); + // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas + // to ensure that the basic fallback function can be called. + let positive_value_cost_stipend = if value > U256::ZERO { 2300 } else { 0 }; + + expect_call( + state, + inner.0.to_alloy(), + inner.3.to_vec(), + Some(value), + None, + Some(inner.2 + positive_value_cost_stipend), + inner.4, + ExpectedCallType::Count, + ) + } + HEVMCalls::MockCall0(inner) => { + // TODO: Does this increase gas usage? + if let Err(err) = data.journaled_state.load_account(inner.0.to_alloy(), data.db) { + return Some(Err(err.into())) + } + + // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` + // check Solidity might perform. + let empty_bytecode = data + .journaled_state + .account(inner.0.to_alloy()) + .info + .code + .as_ref() + .map_or(true, Bytecode::is_empty); + if empty_bytecode { + let code = + Bytecode::new_raw(alloy_primitives::Bytes(bytes::Bytes::from_static(&[0u8]))) + .to_checked(); + data.journaled_state.set_code(inner.0.to_alloy(), code); + } + state.mocked_calls.entry(inner.0.to_alloy()).or_default().insert( + MockCallDataContext { calldata: inner.1.clone().0.into(), value: None }, + MockCallReturnData { + data: inner.2.clone().0.into(), + ret_type: InstructionResult::Return, + }, + ); + Ok(Bytes::new()) + } + HEVMCalls::MockCall1(inner) => { + if let Err(err) = data.journaled_state.load_account(inner.0.to_alloy(), data.db) { + return Some(Err(err.into())) + } + + state.mocked_calls.entry(inner.0.to_alloy()).or_default().insert( + MockCallDataContext { + calldata: inner.2.to_vec().into(), + value: Some(inner.1.to_alloy()), + }, + MockCallReturnData { + data: inner.3.to_vec().into(), + ret_type: InstructionResult::Return, + }, + ); + Ok(Bytes::new()) + } + HEVMCalls::MockCallRevert0(inner) => { + state.mocked_calls.entry(inner.0.to_alloy()).or_default().insert( + MockCallDataContext { calldata: inner.1.to_vec().into(), value: None }, + MockCallReturnData { + data: inner.2.to_vec().into(), + ret_type: InstructionResult::Revert, + }, + ); + Ok(Bytes::new()) + } + HEVMCalls::MockCallRevert1(inner) => { + state.mocked_calls.entry(inner.0.to_alloy()).or_default().insert( + MockCallDataContext { + calldata: inner.2.to_vec().into(), + value: Some(inner.1.to_alloy()), + }, + MockCallReturnData { + data: inner.3.to_vec().into(), + ret_type: InstructionResult::Revert, + }, + ); + Ok(Bytes::new()) + } + HEVMCalls::ClearMockedCalls(_) => { + state.mocked_calls = Default::default(); + Ok(Bytes::new()) + } + HEVMCalls::ExpectSafeMemory(inner) => { + expect_safe_memory(state, inner.0, inner.1, data.journaled_state.depth()) + } + HEVMCalls::ExpectSafeMemoryCall(inner) => { + expect_safe_memory(state, inner.0, inner.1, data.journaled_state.depth() + 1) + } + _ => return None, + }; + Some(result) +} diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 7b389e59f..11b339a31 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -145,7 +145,6 @@ impl TestArgs { let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; let mut filter = self.filter(&config); - trace!(target: "forge::test", ?filter, "using filter"); // Set up the project diff --git a/crates/zkcast/bin/cmd/call.rs b/crates/zkcast/bin/cmd/call.rs index 4cd16e0ac..f2c5159f6 100644 --- a/crates/zkcast/bin/cmd/call.rs +++ b/crates/zkcast/bin/cmd/call.rs @@ -218,6 +218,7 @@ impl CallArgs { } /// fills the builder from create arg +#[allow(clippy::needless_pass_by_ref_mut)] async fn fill_create( builder: &mut TxBuilder<'_, Provider>, value: Option, @@ -240,6 +241,7 @@ async fn fill_create( } /// fills the builder from args +#[allow(clippy::needless_pass_by_ref_mut)] async fn fill_tx( builder: &mut TxBuilder<'_, Provider>, value: Option, diff --git a/crates/zkcast/bin/main.rs b/crates/zkcast/bin/main.rs index f30773c3e..53f513600 100644 --- a/crates/zkcast/bin/main.rs +++ b/crates/zkcast/bin/main.rs @@ -360,7 +360,7 @@ async fn main() -> Result<()> { let sig = match sigs.len() { 0 => eyre::bail!("No signatures found"), - 1 => sigs.get(0).unwrap(), + 1 => sigs.first().unwrap(), _ => { let i: usize = prompt!("Select a function signature by number: ")?; sigs.get(i - 1).ok_or_else(|| eyre::eyre!("Invalid signature index"))? diff --git a/crates/zkcast/src/base.rs b/crates/zkcast/src/base.rs index 016d7402a..3ccaece9c 100644 --- a/crates/zkcast/src/base.rs +++ b/crates/zkcast/src/base.rs @@ -204,7 +204,7 @@ impl Base { /// # Example /// /// ``` -/// use cast::base::NumberWithBase; +/// use zkcast::base::NumberWithBase; /// use ethers_core::types::U256; /// /// let number: NumberWithBase = U256::from(12345).into(); @@ -479,7 +479,7 @@ pub trait ToBase { /// # Example /// /// ``` - /// use cast::base::{Base, ToBase}; + /// use zkcast::base::{Base, ToBase}; /// use ethers_core::types::U256; /// /// // Any type that implements ToBase diff --git a/crates/zkcast/src/lib.rs b/crates/zkcast/src/lib.rs index 659886678..ac164ebd6 100644 --- a/crates/zkcast/src/lib.rs +++ b/crates/zkcast/src/lib.rs @@ -55,7 +55,7 @@ where /// # Example /// /// ``` - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { @@ -73,7 +73,7 @@ where /// # Example /// /// ```no_run - /// use cast::{Cast, TxBuilder}; + /// use zkcast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; @@ -143,7 +143,7 @@ where /// # Example /// /// ```no_run - /// use cast::{Cast, TxBuilder}; + /// use zkcast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; @@ -204,7 +204,7 @@ where /// # Example /// /// ```no_run - /// use cast::{Cast, TxBuilder}; + /// use zkcast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain, U256}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; @@ -246,7 +246,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { @@ -273,7 +273,7 @@ where /// # Example /// /// ```no_run - /// use cast::{Cast, TxBuilder}; + /// use zkcast::{Cast, TxBuilder}; /// use ethers_core::types::{Address, Chain, U256}; /// use ethers_providers::{Provider, Http}; /// use std::{str::FromStr, convert::TryFrom}; @@ -307,7 +307,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { @@ -474,7 +474,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::Address; /// use std::{str::FromStr, convert::TryFrom}; @@ -499,7 +499,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::Address; /// use std::{str::FromStr, convert::TryFrom}; @@ -528,7 +528,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::Address; /// use std::{str::FromStr, convert::TryFrom}; @@ -557,7 +557,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::Address; /// use std::{str::FromStr, convert::TryFrom}; @@ -591,7 +591,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::Address; /// use std::{str::FromStr, convert::TryFrom}; @@ -622,7 +622,7 @@ where /// Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::Address; /// use std::{str::FromStr, convert::TryFrom}; @@ -648,7 +648,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { @@ -690,7 +690,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { @@ -752,7 +752,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { @@ -777,7 +777,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::{Address, H256}; /// use std::{str::FromStr, convert::TryFrom}; @@ -829,7 +829,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_providers::{Provider, Http}; /// use ethers_core::types::{BlockId, BlockNumber}; /// use std::convert::TryFrom; @@ -870,7 +870,7 @@ where /// # Example /// /// ```no_run - /// use cast::Cast; + /// use zkcast::Cast; /// use ethers_core::abi::Address; /// use ethers_providers::{Provider, Ws}; /// use ethers_core::types::Filter; @@ -983,7 +983,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast; + /// # use zkcast::SimpleCast; /// # use ethers_core::types::{I256, U256}; /// assert_eq!(SimpleCast::max_int("uint256")?, format!("{}", U256::MAX)); /// assert_eq!(SimpleCast::max_int("int256")?, format!("{}", I256::MAX)); @@ -999,7 +999,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast; + /// # use zkcast::SimpleCast; /// # use ethers_core::types::{I256, U256}; /// assert_eq!(SimpleCast::min_int("uint256")?, "0"); /// assert_eq!(SimpleCast::min_int("int256")?, format!("{}", I256::MIN)); @@ -1044,7 +1044,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::from_utf8("yo"), "0x796f"); @@ -1063,7 +1063,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::to_ascii("0x796f")?, "yo"); @@ -1083,7 +1083,7 @@ impl SimpleCast { /// Converts fixed point number into specified number of decimals /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// use ethers_core::types::U256; /// /// fn main() -> eyre::Result<()> { @@ -1110,7 +1110,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// use ethers_core::types::U256; /// /// fn main() -> eyre::Result<()> { @@ -1155,7 +1155,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001"); @@ -1179,7 +1179,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// use ethers_core::types::Address; /// use std::str::FromStr; /// @@ -1200,7 +1200,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::to_uint256("100")?, "0x0000000000000000000000000000000000000000000000000000000000000064"); @@ -1223,7 +1223,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::to_int256("0")?, "0x0000000000000000000000000000000000000000000000000000000000000000"); @@ -1253,7 +1253,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::to_unit("1 wei", "wei")?, "1"); @@ -1296,7 +1296,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::from_wei("1", "gwei")?, "0.000000001"); @@ -1322,7 +1322,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::to_wei("1", "")?, "1000000000000000000"); @@ -1346,7 +1346,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::from_rlp("0xc0".to_string()).unwrap(), "[]"); @@ -1368,7 +1368,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::to_rlp("[]").unwrap(),"0xc0".to_string()); @@ -1390,7 +1390,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// use ethers_core::types::{I256, U256}; /// /// fn main() -> eyre::Result<()> { @@ -1425,7 +1425,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// # fn main() -> eyre::Result<()> { /// let bytes = Cast::to_bytes32("1234")?; @@ -1495,7 +1495,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// // Passing `input = false` will decode the data as the output type. @@ -1532,7 +1532,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// // Passing `input = false` will decode the data as the output type. @@ -1567,7 +1567,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast as Cast; + /// # use zkcast::SimpleCast as Cast; /// /// # fn main() -> eyre::Result<()> { /// assert_eq!( @@ -1616,7 +1616,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast as Cast; + /// # use zkcast::SimpleCast as Cast; /// /// # fn main() -> eyre::Result<()> { /// assert_eq!( @@ -1636,8 +1636,8 @@ impl SimpleCast { /// Etherscan. It returns a vector of [`InterfaceSource`] structs that contain the source of the /// interface and their name. /// ```no_run - /// use cast::SimpleCast as Cast; - /// use cast::AbiPath; + /// use zkcast::SimpleCast as Cast; + /// use zkcast::AbiPath; /// # async fn foo() -> eyre::Result<()> { /// let path = AbiPath::Local { /// path: "utils/testdata/interfaceTestABI.json".to_owned(), @@ -1709,7 +1709,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast as Cast; + /// # use zkcast::SimpleCast as Cast; /// /// # fn main() -> eyre::Result<()> { /// @@ -1732,7 +1732,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::namehash("")?, "0x0000000000000000000000000000000000000000000000000000000000000000"); @@ -1768,7 +1768,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::keccak("foo")?, "0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d"); @@ -1795,7 +1795,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::left_shift("16", "10", Some("10".to_string()), "hex")?, "0x4000"); @@ -1825,7 +1825,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::right_shift("0x4000", "10", None, "dec")?, "16"); @@ -1855,7 +1855,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast as Cast; + /// # use zkcast::SimpleCast as Cast; /// # use ethers_core::types::Chain; /// /// # async fn foo() -> eyre::Result<()> { @@ -1884,7 +1884,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast as Cast; + /// # use zkcast::SimpleCast as Cast; /// # use ethers_core::types::Chain; /// # use std::path::PathBuf; /// @@ -1911,7 +1911,7 @@ impl SimpleCast { /// # Example /// /// ```no_run - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let bytecode = "0x608060405260043610603f57600035"; @@ -1930,7 +1930,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// assert_eq!(Cast::get_selector("foo(address,uint256)", None)?.0, String::from("0xbd0d639f")); @@ -1987,7 +1987,7 @@ impl SimpleCast { /// # Example /// /// ``` - /// use cast::SimpleCast as Cast; + /// use zkcast::SimpleCast as Cast; /// /// fn main() -> eyre::Result<()> { /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; diff --git a/crates/zkcast/src/tx.rs b/crates/zkcast/src/tx.rs index 2de54f1e4..249f0b903 100644 --- a/crates/zkcast/src/tx.rs +++ b/crates/zkcast/src/tx.rs @@ -28,7 +28,7 @@ pub type TxBuilderPeekOutput<'a> = (&'a TypedTransaction, &'a Option); /// ``` /// async fn foo() -> eyre::Result<()> { /// use ethers_core::types::{Chain, U256}; -/// use cast::TxBuilder; +/// use zkcast::TxBuilder; /// let provider = ethers_providers::test_provider::MAINNET.provider(); /// let mut builder = TxBuilder::new(&provider, "a.eth", Some("b.eth"), Chain::Mainnet, false).await?; /// builder diff --git a/crates/zkforge/bin/cmd/script/cmd.rs b/crates/zkforge/bin/cmd/script/cmd.rs index 77cd60e78..556422f4f 100644 --- a/crates/zkforge/bin/cmd/script/cmd.rs +++ b/crates/zkforge/bin/cmd/script/cmd.rs @@ -352,7 +352,7 @@ impl ScriptArgs { } if let Some(wallets) = self.wallets.private_keys()? { if wallets.len() == 1 { - script_config.evm_opts.sender = wallets.get(0).unwrap().address().to_alloy() + script_config.evm_opts.sender = wallets.first().unwrap().address().to_alloy() } } Ok(()) diff --git a/crates/zkforge/bin/cmd/script/mod.rs b/crates/zkforge/bin/cmd/script/mod.rs index 205a1da4d..ccaee9cc8 100644 --- a/crates/zkforge/bin/cmd/script/mod.rs +++ b/crates/zkforge/bin/cmd/script/mod.rs @@ -69,7 +69,7 @@ mod runner; mod sequence; pub mod transaction; mod verify; - +#[allow(unused_imports)] pub use transaction::TransactionWithMetadata; /// List of Chains that support Shanghai. diff --git a/crates/zkforge/bin/cmd/test/mod.rs b/crates/zkforge/bin/cmd/test/mod.rs index 4133d4005..04dc6b6fa 100644 --- a/crates/zkforge/bin/cmd/test/mod.rs +++ b/crates/zkforge/bin/cmd/test/mod.rs @@ -131,7 +131,7 @@ impl TestArgs { } pub async fn run(self) -> Result { - trace!(target: "forge::test", "executing test command"); + trace!(target: "zkforge::test", "executing test command"); shell::set_shell(shell::Shell::from_args(self.opts.silent, self.json))?; self.execute_tests().await } @@ -147,8 +147,7 @@ impl TestArgs { let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; let mut filter = self.filter(&config); - - trace!(target: "forge::test", ?filter, "using filter"); + trace!(target: "zkforge::test", ?filter, "using filter"); // Set up the project let mut project = config.project()?; @@ -164,7 +163,7 @@ impl TestArgs { // Create test options from general project settings // and compiler output - let project_root = project.paths.root.join("zkout"); + let project_root = &project.paths.root.clone(); let toml = config.get_config_path(); let profiles = get_available_profiles(toml)?; @@ -186,7 +185,7 @@ impl TestArgs { .fuzz(config.fuzz) .invariant(config.invariant) .profiles(profiles) - .build(&output, &project_root)?; + .build(&output, project_root)?; // Determine print verbosity and executor verbosity let verbosity = evm_opts.verbosity; @@ -209,7 +208,7 @@ impl TestArgs { .with_test_options(test_options.clone()); let mut runner = runner_builder.clone().build( - &project_root, + project_root, output.clone(), env.clone(), evm_opts.clone(), @@ -226,7 +225,7 @@ impl TestArgs { } let test_funcs = runner.get_matching_test_functions(&filter); // if we debug a fuzz test, we should not collect data on the first run - if !test_funcs.get(0).expect("matching function exists").inputs.is_empty() { + if !test_funcs.first().expect("matching function exists").inputs.is_empty() { runner_builder = runner_builder.set_debug(false); runner = runner_builder.clone().build( project_root, @@ -642,13 +641,12 @@ async fn test( summary: bool, detailed: bool, ) -> Result { - trace!(target: "forge::test", "running all tests"); - + trace!(target: "zkforge::test", "running all tests"); if runner.matching_test_function_count(&filter) == 0 { let filter_str = filter.to_string(); if filter_str.is_empty() { println!( - "\nNo tests found in project! Forge looks for functions that starts with `test`." + "\nNo tests found in project! zkforge looks for functions that starts with `test`." ); } else { println!("\nNo tests match the provided pattern:"); diff --git a/crates/zkforge/bin/cmd/zk_solc.rs b/crates/zkforge/bin/cmd/zk_solc.rs index 24b9621c0..8ccdfd9d2 100644 --- a/crates/zkforge/bin/cmd/zk_solc.rs +++ b/crates/zkforge/bin/cmd/zk_solc.rs @@ -51,7 +51,7 @@ use std::{ fmt, fs, fs::File, io::Write, - path::PathBuf, + path::{Path, PathBuf}, process::{exit, Command, Stdio}, }; @@ -305,7 +305,7 @@ impl ZkSolc { } } let mut result = ProjectCompileOutput::default(); - result.compiled_artifacts = Artifacts { 0: data }; + result.compiled_artifacts = Artifacts(data); Ok(result) } @@ -403,8 +403,8 @@ impl ZkSolc { // First - let's get all the bytecodes. let mut all_bytecodes: HashMap = Default::default(); - for (_source_file_name, source_file_results) in &compiler_output.contracts { - for (_contract_name, contract_results) in source_file_results { + for source_file_results in compiler_output.contracts.values() { + for contract_results in source_file_results.values() { if let Some(hash) = &contract_results.hash { all_bytecodes.insert( hash.clone(), @@ -443,25 +443,27 @@ impl ZkSolc { .to_vec(), ); - let mut art = ConfigurableContractArtifact::default(); - art.bytecode = Some(CompactBytecode { - object: ethers::solc::artifacts::BytecodeObject::Bytecode( - packed_bytecode.clone(), - ), - source_map: None, - link_references: Default::default(), - }); - - art.deployed_bytecode = Some(CompactDeployedBytecode { + let mut art = ConfigurableContractArtifact { bytecode: Some(CompactBytecode { object: ethers::solc::artifacts::BytecodeObject::Bytecode( - packed_bytecode, + packed_bytecode.clone(), ), source_map: None, link_references: Default::default(), }), - immutable_references: Default::default(), - }); + deployed_bytecode: Some(CompactDeployedBytecode { + bytecode: Some(CompactBytecode { + object: ethers::solc::artifacts::BytecodeObject::Bytecode( + packed_bytecode, + ), + source_map: None, + link_references: Default::default(), + }), + immutable_references: Default::default(), + }), + // Initialize other fields with their default values if they exist + ..ConfigurableContractArtifact::default() + }; art.abi = contract.abi.clone(); @@ -790,7 +792,7 @@ impl ZkSolc { /// This function can return an error if any of the following occurs: /// - The extraction of the filename from the contract source path fails. /// - The creation of the artifacts directory fails. - fn build_artifacts_path(&self, source: &PathBuf) -> Result { + fn build_artifacts_path(&self, source: &Path) -> Result { let filename = source.file_name().expect("Failed to get Contract filename."); let path = self.project.paths.artifacts.join(filename); fs::create_dir_all(&path).wrap_err("Could not create artifacts directory")?; diff --git a/docs/dev/zksync/zkcast-usage.md b/docs/dev/zksync/zkcast-usage.md new file mode 100644 index 000000000..55f8acb20 --- /dev/null +++ b/docs/dev/zksync/zkcast-usage.md @@ -0,0 +1,143 @@ +# zkcast Usage Guide + +Welcome to the zkcast usage guide! This document will provide you with detailed instructions on how to use various commands within zkcast to interact with blockchain, bridge assets between L1 and L2, and interact with contracts. The guide is structured for clarity and ease of use. + +## Setting Up + +### Spin up Local Docker Node + +- Follow the [Instructions to setup local Docker node](https://era.zksync.io/docs/tools/testing/dockerized-testing.html). + +## Basic Blockchain Interactions + +### Get Chain ID + +- **Local Node:** + ```sh + zkcast chain-id --rpc-url http://localhost:3050 + ``` + **Output:** `270` + +- **Testnet:** + ```sh + zkcast chain-id --rpc-url https://zksync2-testnet.zksync.dev:443 + ``` + **Output:** `280` + +### Get Client Information + +- **Command:** + ```sh + zkcast client --rpc-url https://zksync2-testnet.zksync.dev:443 + ``` + **Output:** `zkSync/v2.0` + +### Get Account's L2 ETH Balance + +- **Command:** + ```sh + zkcast balance 0x42C7eF198f8aC9888E2B1b73e5B71f1D4535194A --rpc-url https://zksync2-testnet.zksync.dev:443 + ``` + **Output:** `447551277794355871` + +### Get Gas Price + +- **Command:** + ```sh + zkcast gas-price --rpc-url https://zksync2-testnet.zksync.dev:443 + ``` + **Example Output:** `250000000` + +### Get Latest Block + +- **Command:** + ```sh + zkcast block latest --rpc-url https://zksync2-testnet.zksync.dev:443 + ``` + **Example Output:** + ```sh + baseFeePerGas 250000000 + ... + l1BatchTimestamp null + ``` + +## Bridging Assets Between L1 and L2 + +### L1 β†’ L2 Deposits + +- **Command:** + ```sh + zkcast zk-deposit --l1-rpc-url --l2-url --chain --private-key + ``` + **Note:** Leave `` blank to bridge ETH. + + **Example (Error Case):** + ```sh + zkcast zkdeposit 0x36615Cf349d7F6344891B1e7CA7C72883F5dc049 1000000 --rpc-url http://localhost:8545 --l2-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 + ``` + **Output:** `Bridging assets.... Transaction Hash: 0x55793df0a636aedd098309e3487c6d9ec0910422d5b9f0bdbdf764bc82dc1b9f` + +### L2 β†’ L1 Withdrawals + +- **Command:** + ```sh + zkcast zk-send --withdraw --amount --rpc-url --private-key + ``` + **Example:** + ```sh + zkcast zk-send --withdraw 0x36615Cf349d7F6344891B1e7CA7C72883F5dc049 --amount 1000000 --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 + ``` + **Output:** + ``` + Bridging assets.... + Transaction Hash: 0x3562f47db61de149fb7266c3a65935c4e8324cceb5a1db8718390a8a5a210191 + Gas used: 10276475 + Effective gas price: 250000000 + Block Number: 6652714 + ``` + +## Interacting with Contracts + +### General Usage + +- **Aliases:** `zkcast zks`, `zkcast zksend` +- **Purpose:** Interact with deployed contracts in the native foundry/zkforge fashion using the CLI `zkcast zk-send` command. +- **Scope:** Retrieve and interact with chain data, such as block numbers and gas estimates. Interact with deployed contracts on zkSync Era testnet or local Docker node. + +### Non-state Changing Calls + +- **Command:** + ```sh + zkcast call --rpc-url + ``` + **Example:** + ```bash + zkcast call 0x97b985951fd3e0c1d996421cc783d46c12d00082 "greet()(string)" --rpc-url http://localhost:3050 + ``` + **Output:** `ZkSync + Pineapple` + +### Send Transactions + +- **Command:** + ```sh + zkcast zk-send --rpc-url --private-key --chain + ``` + **Example:** + ```sh + zkcast zk-send 0x97b985951fd3e0c1d996421cc783d46c12d00082 "setGreeting(string)" "Killer combo!" --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 + ``` + **Output:** + ``` + Sending transaction.... + Transaction Hash: 0x7651fba8ddeb624cca93f89da493675ccbc5c6d36ee25ed620b07424ce338552 + ``` + +### Verify Output + +- **Command:** + ```sh + zkcast call 0x97b985951fd3e0c1d996421cc783d46c12d00082 "greet()(string)" --rpc-url http://localhost:3050 + ``` + **Output:** `Killer combo!` + +This guide provides a comprehensive overview of the commands available in zkcast for various blockchain interactions, bridging assets \ No newline at end of file diff --git a/docs/dev/zksync/zkforge-usage.md b/docs/dev/zksync/zkforge-usage.md new file mode 100644 index 000000000..8347dca9d --- /dev/null +++ b/docs/dev/zksync/zkforge-usage.md @@ -0,0 +1,146 @@ +# zkforge: Command Guide for Compilation and Deployment + +### Compilation with `zkforge zk-build` + +**Aliases:** `zkforge zkbuild`, `zkforge zk-compile`, `zkforge zkb`. + +**Function:** Compiles smart contracts into zkEVM bytecode, outputting files into a structured directory `/zkout/`. + +**Usage:** + +```sh +zkforge zk-build [OPTIONS] +``` + +**Options:** + +- `--use-zksolc`: Specify zksolc compiler version (default if left blank). +- `--is-system`: Enables system contract compilation mode. +- `--force-evmla`: Forces the EVM legacy assembly pipeline. +- `-h, --help`: Prints help. + +**Note:** The `--is-system` flag is essential for contracts like factory contracts. These should be in `src/is-system/`. Create the folder if it doesn't exist. + +![System Contracts Folder](https://user-images.githubusercontent.com/76663878/236301037-2a536ab0-3d09-44f3-a74d-5f5891af335b.png) + +**Example Usage:** + +Compile with default compiler options (v1.3.11). + +```sh +zkforge zk-build +``` + +**Compiler Settings:** + +Set `zksolc` compiler version using `--use` flag. + +```bash +zkforge zkb --use 0.8.19 +``` + +**Example Output:** + +`zksolc` compiler artifacts location: + +```bash +/zkout/ +``` +![Compiler Artifacts](https://user-images.githubusercontent.com/76663878/234152279-e144e489-41ab-4cbd-8321-8ccd9b0aa6ef.png) + +Example terminal output: + +![Terminal Output](https://user-images.githubusercontent.com/76663878/236305625-8c7519e2-0c5e-492f-a4bc-3b019a95e34f.png) + +**Important:** Until `forge remappings` are implemented, use relative import paths: + +![Import Paths](https://github.com/matter-labs/foundry-zksync/assets/76663878/490b34f4-e286-42a7-8570-d4b228ec10c7) + +`SimpleFactory.sol` and `AAFactory.sol` should be in `src/is-system/`. + +--- + +### Deployment with `zkforge zk-create` + +**Aliases:** `zkforge zkcreate`, `zkforge zk-deploy`, `zkforge zkc` + +**Function:** Deploys smart contracts to zksync. + +**Usage:** + +```sh +zkforge zk-create [OPTIONS] --rpc-url --chain --private-key +``` + +**Options:** + +- `-h, --help`: Prints help. +- `--constructor-args ...`: Constructor arguments. +- `--constructor-args-path `: File path containing constructor arguments. +- ``: Contract identifier in `:` form. +- `--factory-deps ...`: Factory dependencies in `:` form. + +**Example:** + +Deploy `src/Greeter.sol` to zkSync testnet: + +```bash +zkforge zkc src/Greeter.sol:Greeter --constructor-args "ZkSync + Pineapple" --private-key <"PRIVATE_KEY"> --rpc-url https://zksync2-testnet.zksync.dev:443 --chain 280 +``` + +**Output:** + +```txt +Deploying contract... ++-------------------------------------------------+ +Contract successfully deployed to address: 0x07d485ff2df314b240ec392ed86b137a661ddd35 +Transaction Hash: 0xdb6864fe1d19572a3ff509c5c7ed43f033d2dab8261a843808ed46e6e6ee51be +Gas used: 89879008 +Effective gas price: 250000000 +Block Number: 6651906 ++-------------------------------------------------+ +``` + +--- + +## Deploy and Interact with `SimpleFactory.sol` + +### Compile `SimpleFactory.sol` + +**Note:** Compile with the `is-system` flag; place in `src/is-system/`. + +```bash +zkforge zk-build +``` + +### Deploy `SimpleFactory.sol` + +```sh +zkforge zkc src/SimpleFactory.sol:SimpleFactory --constructor-args 01000041691510d85ddfc6047cba6643748dc028636d276f09a546ab330697ef 010000238a587670be26087b7812eab86eca61e7c4014522bdceda86adb2e82f --factory-deps src/Child.sol:Child src/StepChild.sol:StepChild --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --rpc-url http://localhost:3050 --chain 270 +``` + +**Output:** + +```txt +Deploying contract... ++-------------------------------------------------+ +Contract successfully deployed to address: 0xa1b809005e589f81de6ef9f48d67e35606c05fc3 +Transaction Hash: 0x34782985ba7c70b6bc4a8eb2b95787baec29356171fdbb18608037a2fcd7eda8 +Gas used: 168141 +Effective gas price: 250000000 +Block Number: 249 ++-------------------------------------------------+ +``` + +### Deploy `StepChild.sol` via `SimpleFactory.sol` + +```sh +zkcast zk-send 0x23cee3fb585b1e5092b7cfb222e8e873b05e9519 "newStepChild()" --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 +``` + +**Output:** + +```sh +Sending transaction.... +Transaction Hash: 0xa82a0636b71af058d4916d81868eebc41173ca07b78d30fe57f4b74e9294ef25 +``` \ No newline at end of file diff --git a/docs/dev/zksync/zksync-aa-usage.md b/docs/dev/zksync/zksync-aa-usage.md new file mode 100644 index 000000000..fe5fcdc17 --- /dev/null +++ b/docs/dev/zksync/zksync-aa-usage.md @@ -0,0 +1,93 @@ +## Guide to Deploying and Interacting with Account Abstraction Multisig Contracts on zkSync Era + +In this guide, we'll go through the process of compiling, deploying, and interacting with contracts for account abstraction multisig on the zkSync Era platform. We'll work with two contracts: `AAFactory.sol` and `TwoUserMultiSig.sol`. + +### Step 1: Compile `AAFactory.sol` + +First, compile `AAFactory.sol` using the `--is-system` flag because it interacts with system contracts for deploying multisig wallets. + +**Location:** Place the contract in the `src/is-system/` folder. + +**Command:** +```sh +../foundry-zksync/target/debug/zkforge zk-build +``` + +**Expected Output:** +```sh +AAFactory -> Bytecode Hash: "010000791703a54dbe2502b00ee470989c267d0f6c0d12a9009a947715683744" +Compiled Successfully +``` + +### Step 2: Deploy `AAFactory.sol` + +To deploy the factory, use the Bytecode Hash of `TwoUserMultiSig.sol` in the constructor of `AAFactory.sol`. + +**Note:** `aaBytecodeHash` equals the Bytecode hash of `TwoUserMultiSig.sol`. + +**Command:** +```sh +../foundry-zksync/target/debug/zkforge zkc src/is-system/AAFactory.sol:AAFactory --constructor-args 010007572230f4df5b4e855ff48d4cdfffc9405522117d7e020ee42650223460 --factory-deps src/TwoUserMultiSig.sol:TwoUserMultisig --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --rpc-url http://localhost:3050 --chain 270 +``` + +**Expected Output:** +```sh +Deploying contract... +Contract successfully deployed to address: 0xd5608cec132ed4875d19f8d815ec2ac58498b4e5 +Transaction Hash: 0x0e6f55ff1619af8b3277853a8f2941d0481635880358316f03ae264e2de059ed +Gas used: 154379 +Effective gas price: 250000000 +Block Number: 291 +``` + +### Step 3: Deploy `TwoUserMultiSig.sol` Instance + +Now, deploy a new `TwoUserMultiSig.sol` instance using the `deployAccount` function of `AAFactory.sol`. + +**Required Parameters:** +- **owner1:** `0xa61464658AfeAf65CccaaFD3a512b69A83B77618` +- **owner2:** `0x0D43eB5B8a47bA8900d84AA36656c92024e9772e` +- **salt:** `0x00` (unique value needed for each instance using the same owner wallets). + +**Command:** +```sh +../foundry-zksync/target/debug/zkcast zk-send 0xd5608cec132ed4875d19f8d815ec2ac58498b4e5 "deployAccount(bytes32,address,address)(address)" 0x00 0xa61464658AfeAf65CccaaFD3a512b69A83B77618 0x0D43eB5B8a47bA8900d84AA36656c92024e9772e --rpc-url http://localhost:3050 --private-key 7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --chain 270 +``` + +**Expected Output:** +```sh +Sending transaction.... +Transaction Hash: 0x43a4dded84a12891dfae4124b42b9f091750e953193bd779a7e5e4d422909e73 +0x03e50ec034f1d363de0add752c33d4831a2731bf, <---- Deployed contract address +``` + +### Step 4: Verify the Deployment + +Check the transaction receipt and verify the owners of the deployed `TwoUserMultiSig.sol` contract. + +**Command to Check Transaction Receipt:** +```sh +../foundry-zksync/target/debug/zkcast tx 0x22364a3e191ad10013c5f20036e9696e743a4f686bc58a0106ef0b9e7592347c --rpc-url http://localhost:3050 +``` + +**Verify `owner1`:** +```sh +../foundry-zksync/target/debug/zkcast call 0x03e50ec034f1d363de0add752c33d4831a2731bf "owner1()(address)" --rpc-url http://localhost:3050 +``` + +**Expected Output for `owner1`:** +```txt +0xa61464658AfeAf65CccaaFD3a512b69A83B77618 +``` + +**Verify `owner2`:** +```sh +../foundry-zksync/target/debug/zkcast call 0x03e50ec034f1d363de0add752c33d4831a2731bf "owner2()(address)" --rpc-url http://localhost:3050 +``` + +**Expected Output for `owner2`:** +```txt +0x0D43eB5B8a47bA8900d84AA36656c92024e9772e +``` + +With these steps completed, you should have successfully deployed and verified a `TwoUserMultiSig.sol` contract instance on zkSync Era. \ No newline at end of file