diff --git a/.gitignore b/.gitignore index c7cd9661..192709a5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ node_modules/ # era-test-node era_test_node.log +anvil-zksync.log package-lock.json yarn.lock diff --git a/README.md b/README.md index 337c058c..628b6690 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ complete developer solution, this project is just the smart contract components. 1. Install workspace dependencies with `pnpm install`. 2. Install the latest release of - [Era Test Node](https://github.com/matter-labs/era-test-node). + [Era Test Node](https://github.com/matter-labs/anvil-zksync). 3. Run `pnpm build` to build the contracts. 4. Run `era_test_node run` and `pnpm test` in separate terminals to run the tests. diff --git a/hardhat.config.ts b/hardhat.config.ts index 17a15383..72650e9a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -2,6 +2,7 @@ import "@typechain/hardhat"; import "@matterlabs/hardhat-zksync"; import "@nomicfoundation/hardhat-chai-matchers"; import "./scripts/deploy"; +import "./scripts/upgrade"; import { HardhatUserConfig } from "hardhat/config"; diff --git a/package.json b/package.json index f4a3cb65..ae08bd0d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "build": "pnpm hardhat compile", "clean": "pnpm hardhat clean", "test": "pnpm hardhat test", - "deploy": "pnpm hardhat deploy" + "deploy": "pnpm hardhat deploy", + "upgrade": "pnpm hardhat upgrade" }, "devDependencies": { "@commitlint/cli": "19.5.0", @@ -64,7 +65,7 @@ "eslint-plugin-simple-import-sort": "12.1.1", "ethers": "6.13.2", "globals": "15.9.0", - "hardhat": "^2.22.12", + "hardhat": "^2.22.17", "husky": "9.1.6", "ini": "5.0.0", "lint-staged": "15.2.10", @@ -74,14 +75,14 @@ "nx": "19.8.6", "prettier": "3.3.3", "prettier-plugin-solidity": "^1.4.1", + "solady": "^0.0.273", "ts-node": "10.9.2", "typechain": "8.3.2", "typescript": "5.6.2", "typescript-eslint": "8.7.0", "viem": "^2.21.14", "zksync-ethers": "6.15.0", - "zksync-sso": "0.0.0-beta.2", - "solady": "^0.0.273" + "zksync-sso": "0.0.0-beta.2" }, "packageManager": "pnpm@9.11.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55695e44..118779c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,10 +31,10 @@ importers: version: 0.2.2 '@matterlabs/hardhat-zksync': specifier: 1.2.0 - version: 1.2.0(usgi36jxwmcpnzhrk43hjntexu) + version: 1.2.0(fepeho5lbjwfeubrlam6jwj3im) '@matterlabs/hardhat-zksync-deploy': specifier: 1.5.0 - version: 1.5.0(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) + version: 1.5.0(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) '@matterlabs/zksync-contracts': specifier: ^0.6.1 version: 0.6.1(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@4.9.6) @@ -43,16 +43,16 @@ importers: version: 0.0.1-rc.1 '@nomicfoundation/hardhat-chai-matchers': specifier: 2.0.8 - version: 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + version: 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@nomicfoundation/hardhat-ethers': specifier: 3.0.8 - version: 3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + version: 3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@nomicfoundation/hardhat-toolbox': specifier: ^5.0.0 - version: 5.0.0(cfo2tkb7o3545vb5w3un5hokvy) + version: 5.0.0(g4q45fufb3cpd4wckvbqanslk4) '@nomicfoundation/hardhat-verify': specifier: 2.0.11 - version: 2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + version: 2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@openzeppelin/contracts': specifier: 4.9.6 version: 4.9.6 @@ -88,7 +88,7 @@ importers: version: 0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2) '@typechain/hardhat': specifier: 9.1.0 - version: 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2)) + version: 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2)) '@types/chai': specifier: 4.3.20 version: 4.3.20 @@ -132,8 +132,8 @@ importers: specifier: 15.9.0 version: 15.9.0 hardhat: - specifier: ^2.22.12 - version: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + specifier: ^2.22.17 + version: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) husky: specifier: 9.1.6 version: 9.1.6 @@ -4205,8 +4205,8 @@ packages: peerDependencies: hardhat: ^2.0.2 - hardhat@2.22.16: - resolution: {integrity: sha512-d52yQZ09u0roL6GlgJSvtknsBtIuj9JrJ/U8VMzr/wue+gO5v2tQayvOX6llerlR57Zw2EOTQjLAt6RpHvjwHA==} + hardhat@2.22.17: + resolution: {integrity: sha512-tDlI475ccz4d/dajnADUTRc1OJ3H8fpP9sWhXhBPpYsQOg8JHq5xrDimo53UhWPl7KJmAeDCm1bFG74xvpGRpg==} hasBin: true peerDependencies: ts-node: '*' @@ -8011,15 +8011,15 @@ snapshots: - encoding - supports-color - '@matterlabs/hardhat-zksync-deploy@1.5.0(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2))': + '@matterlabs/hardhat-zksync-deploy@1.5.0(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2))': dependencies: - '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) chai: 4.5.0 chalk: 4.1.2 ethers: 6.13.2 fs-extra: 11.2.0 glob: 10.4.5 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) lodash: 4.17.21 sinon: 18.0.1 sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1) @@ -8029,15 +8029,15 @@ snapshots: - encoding - supports-color - '@matterlabs/hardhat-zksync-deploy@1.6.0(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2))': + '@matterlabs/hardhat-zksync-deploy@1.6.0(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2))': dependencies: - '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) chai: 4.5.0 chalk: 4.1.2 ethers: 6.13.2 fs-extra: 11.2.0 glob: 10.4.5 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) lodash: 4.17.21 sinon: 18.0.1 sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1) @@ -8049,13 +8049,13 @@ snapshots: '@matterlabs/hardhat-zksync-ethers@1.2.1(ethers@6.13.2)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(zksync-ethers@6.15.0(ethers@6.13.2))': dependencies: - '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) - '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) chai: 4.5.0 chalk: 4.1.2 ethers: 6.13.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) zksync-ethers: 6.15.0(ethers@6.13.2) transitivePeerDependencies: - bufferutil @@ -8066,15 +8066,15 @@ snapshots: - typescript - utf-8-validate - '@matterlabs/hardhat-zksync-node@1.2.0(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@matterlabs/hardhat-zksync-node@1.2.0(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: - '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) axios: 1.7.7(debug@4.3.7) chai: 4.5.0 chalk: 4.1.2 debug: 4.3.7(supports-color@9.4.0) fs-extra: 11.2.0 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) proxyquire: 2.1.3 sinon: 18.0.1 sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1) @@ -8084,7 +8084,7 @@ snapshots: - encoding - supports-color - '@matterlabs/hardhat-zksync-solc@1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@matterlabs/hardhat-zksync-solc@1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: '@nomiclabs/hardhat-docker': 2.0.2 chai: 4.5.0 @@ -8092,7 +8092,7 @@ snapshots: debug: 4.3.7(supports-color@9.4.0) dockerode: 4.0.2 fs-extra: 11.2.0 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) proper-lockfile: 4.1.2 semver: 7.6.3 sinon: 18.0.1 @@ -8102,23 +8102,23 @@ snapshots: - encoding - supports-color - '@matterlabs/hardhat-zksync-upgradable@1.8.2(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)': + '@matterlabs/hardhat-zksync-upgradable@1.8.2(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)': dependencies: - '@matterlabs/hardhat-zksync-deploy': 1.6.0(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) + '@matterlabs/hardhat-zksync-deploy': 1.6.0(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) '@matterlabs/hardhat-zksync-ethers': 1.2.1(ethers@6.13.2)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(zksync-ethers@6.15.0(ethers@6.13.2)) - '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@openzeppelin/contracts-hardhat-zksync-upgradable': '@openzeppelin/contracts@5.1.0' '@openzeppelin/defender-sdk-base-client': 1.15.2 '@openzeppelin/defender-sdk-deploy-client': 1.15.2(debug@4.3.7) '@openzeppelin/defender-sdk-network-client': 1.15.2(debug@4.3.7) - '@openzeppelin/hardhat-upgrades': 3.5.0(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@openzeppelin/hardhat-upgrades': 3.5.0(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@openzeppelin/upgrades-core': 1.40.0 chalk: 4.1.2 compare-versions: 6.1.1 ethereumjs-util: 7.1.5 ethers: 6.13.2 fs-extra: 11.2.0 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) proper-lockfile: 4.1.2 semver: 7.6.3 solidity-ast: 0.4.59 @@ -8135,18 +8135,18 @@ snapshots: - typescript - utf-8-validate - '@matterlabs/hardhat-zksync-verify@1.7.0(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@matterlabs/hardhat-zksync-verify@1.7.0(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 - '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) axios: 1.7.7(debug@4.3.7) cbor: 9.0.2 chai: 4.5.0 chalk: 4.1.2 debug: 4.3.7(supports-color@9.4.0) - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) semver: 7.6.3 sinon: 18.0.1 sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1) @@ -8154,20 +8154,20 @@ snapshots: - encoding - supports-color - '@matterlabs/hardhat-zksync@1.2.0(usgi36jxwmcpnzhrk43hjntexu)': + '@matterlabs/hardhat-zksync@1.2.0(fepeho5lbjwfeubrlam6jwj3im)': dependencies: - '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) + '@matterlabs/hardhat-zksync-deploy': 1.5.0(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(zksync-ethers@6.15.0(ethers@6.13.2)) '@matterlabs/hardhat-zksync-ethers': 1.2.1(ethers@6.13.2)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)(zksync-ethers@6.15.0(ethers@6.13.2)) - '@matterlabs/hardhat-zksync-node': 1.2.0(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@matterlabs/hardhat-zksync-upgradable': 1.8.2(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) - '@matterlabs/hardhat-zksync-verify': 1.7.0(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-node': 1.2.0(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@matterlabs/hardhat-zksync-upgradable': 1.8.2(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + '@matterlabs/hardhat-zksync-verify': 1.7.0(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@openzeppelin/upgrades-core': 1.40.0 chai: 4.5.0 ethers: 6.13.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) sinon: 18.0.1 sinon-chai: 3.7.0(chai@4.5.0)(sinon@18.0.1) zksync-ethers: 6.15.0(ethers@6.13.2) @@ -8296,43 +8296,43 @@ snapshots: '@nomicfoundation/ethereumjs-rlp': 5.0.4 ethereum-cryptography: 0.1.3 - '@nomicfoundation/hardhat-chai-matchers@2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@nomicfoundation/hardhat-chai-matchers@2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@types/chai-as-promised': 7.1.8 chai: 4.5.0 chai-as-promised: 7.1.2(chai@4.5.0) deep-eql: 4.1.4 ethers: 6.13.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) ordinal: 1.0.3 - '@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: debug: 4.3.7(supports-color@9.4.0) ethers: 6.13.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) lodash.isequal: 4.5.0 transitivePeerDependencies: - supports-color - '@nomicfoundation/hardhat-ignition-ethers@0.15.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-ignition@0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/ignition-core@0.15.8)(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@nomicfoundation/hardhat-ignition-ethers@0.15.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-ignition@0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/ignition-core@0.15.8)(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-ignition': 0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ignition': 0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@nomicfoundation/ignition-core': 0.15.8 ethers: 6.13.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) - '@nomicfoundation/hardhat-ignition@0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@nomicfoundation/hardhat-ignition@0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: - '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@nomicfoundation/ignition-core': 0.15.8 '@nomicfoundation/ignition-ui': 0.15.8 chalk: 4.1.2 debug: 4.3.7(supports-color@9.4.0) fs-extra: 10.1.0 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) json5: 2.2.3 prompts: 2.4.2 transitivePeerDependencies: @@ -8340,40 +8340,40 @@ snapshots: - supports-color - utf-8-validate - '@nomicfoundation/hardhat-network-helpers@1.0.12(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@nomicfoundation/hardhat-network-helpers@1.0.12(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: ethereumjs-util: 7.1.5 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) - '@nomicfoundation/hardhat-toolbox@5.0.0(cfo2tkb7o3545vb5w3un5hokvy)': + '@nomicfoundation/hardhat-toolbox@5.0.0(g4q45fufb3cpd4wckvbqanslk4)': dependencies: - '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-ignition-ethers': 0.15.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-ignition@0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/ignition-core@0.15.8)(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-network-helpers': 1.0.12(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-chai-matchers': 2.0.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(chai@4.5.0)(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ignition-ethers': 0.15.8(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-ignition@0.15.8(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/ignition-core@0.15.8)(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-network-helpers': 1.0.12(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@typechain/ethers-v6': 0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2) - '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2)) + '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2)) '@types/chai': 4.3.20 '@types/mocha': 10.0.8 '@types/node': 20.16.10 chai: 4.5.0 ethers: 6.13.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) - hardhat-gas-reporter: 1.0.10(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) - solidity-coverage: 0.8.13(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat-gas-reporter: 1.0.10(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + solidity-coverage: 0.8.13(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2) typechain: 8.3.2(typescript@5.6.2) typescript: 5.6.2 - '@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/address': 5.7.0 cbor: 8.1.0 chalk: 2.4.2 debug: 4.3.7(supports-color@9.4.0) - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) lodash.clonedeep: 4.5.0 semver: 6.3.1 table: 6.8.2 @@ -8740,9 +8740,9 @@ snapshots: - debug - encoding - '@openzeppelin/hardhat-upgrades@3.5.0(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': + '@openzeppelin/hardhat-upgrades@3.5.0(@nomicfoundation/hardhat-ethers@3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(@nomicfoundation/hardhat-verify@2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)))(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))': dependencies: - '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-ethers': 3.0.8(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) '@openzeppelin/defender-sdk-base-client': 1.15.2 '@openzeppelin/defender-sdk-deploy-client': 1.15.2(debug@4.3.7) '@openzeppelin/defender-sdk-network-client': 1.15.2(debug@4.3.7) @@ -8751,11 +8751,11 @@ snapshots: debug: 4.3.7(supports-color@9.4.0) ethereumjs-util: 7.1.5 ethers: 6.13.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) proper-lockfile: 4.1.2 undici: 6.21.0 optionalDependencies: - '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) + '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)) transitivePeerDependencies: - encoding - supports-color @@ -9331,12 +9331,12 @@ snapshots: typechain: 8.3.2(typescript@5.6.2) typescript: 5.6.2 - '@typechain/hardhat@9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@6.13.2)(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2))': + '@typechain/hardhat@9.1.0(@typechain/ethers-v6@0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2))(ethers@6.13.2)(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2))(typechain@8.3.2(typescript@5.6.2))': dependencies: '@typechain/ethers-v6': 0.5.1(ethers@6.13.2)(typechain@8.3.2(typescript@5.6.2))(typescript@5.6.2) ethers: 6.13.2 fs-extra: 9.1.0 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) typechain: 8.3.2(typescript@5.6.2) '@types/bn.js@4.11.6': @@ -11648,11 +11648,11 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - hardhat-gas-reporter@1.0.10(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)): + hardhat-gas-reporter@1.0.10(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)): dependencies: array-uniq: 1.0.3 eth-gas-reporter: 0.2.27 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) sha1: 1.1.1 transitivePeerDependencies: - '@codechecks/client' @@ -11660,7 +11660,7 @@ snapshots: - debug - utf-8-validate - hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2): + hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1 @@ -13642,7 +13642,7 @@ snapshots: solidity-ast@0.4.59: {} - solidity-coverage@0.8.13(hardhat@2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)): + solidity-coverage@0.8.13(hardhat@2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2)): dependencies: '@ethersproject/abi': 5.7.0 '@solidity-parser/parser': 0.18.0 @@ -13653,7 +13653,7 @@ snapshots: ghost-testrpc: 0.0.2 global-modules: 2.0.0 globby: 10.0.2 - hardhat: 2.22.16(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) + hardhat: 2.22.17(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.15))(@types/node@20.16.10)(typescript@5.6.2))(typescript@5.6.2) jsonschema: 1.4.1 lodash: 4.17.21 mocha: 10.7.3 diff --git a/scripts/deploy.ts b/scripts/deploy.ts index cb8ec9a7..e0e2f1b1 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -4,24 +4,19 @@ import { ethers } from "ethers"; import { task } from "hardhat/config"; import { Wallet } from "zksync-ethers"; -const ethersStaticSalt = new Uint8Array([ - 205, 241, 161, 186, 101, 105, 79, - 248, 98, 64, 50, 124, 168, 204, - 200, 71, 214, 169, 195, 118, 199, - 62, 140, 111, 128, 47, 32, 21, - 177, 177, 174, 166, -]); +const WEBAUTH_NAME = "WebAuthValidator"; const SESSIONS_NAME = "SessionKeyValidator"; const ACCOUNT_IMPL_NAME = "SsoAccount"; -const AA_FACTORY_NAME = "AAFactory"; +const FACTORY_NAME = "AAFactory"; const PAYMASTER_NAME = "ExampleAuthServerPaymaster"; +const BEACON_NAME = "SsoBeacon"; async function deploy(name: string, deployer: Wallet, proxy: boolean, args?: any[]): Promise { // eslint-disable-next-line @typescript-eslint/no-require-imports - const { deployFactory, create2 } = require("../test/utils"); + const { deployFactory, create2, ethersStaticSalt } = require("../test/utils"); console.log("Deploying", name, "contract..."); let implContract; - if (name == AA_FACTORY_NAME) { + if (name == FACTORY_NAME) { implContract = await deployFactory(deployer, args![0]); } else { implContract = await create2(name, deployer, ethersStaticSalt, args); @@ -37,13 +32,14 @@ async function deploy(name: string, deployer: Wallet, proxy: boolean, args?: any return proxyAddress; } + task("deploy", "Deploys ZKsync SSO contracts") - .addOptionalParam("privatekey", "private key to the account to deploy the contracts from") .addOptionalParam("only", "name of a specific contract to deploy") .addFlag("noProxy", "do not deploy transparent proxies for factory and modules") - .addOptionalParam("implementation", "address of the account implementation to use in the factory") + .addOptionalParam("implementation", "address of the account implementation to use in the beacon") .addOptionalParam("factory", "address of the factory to use in the paymaster") .addOptionalParam("sessions", "address of the sessions module to use in the paymaster") + .addOptionalParam("beacon", "address of the beacon to use in the factory") .addOptionalParam("fund", "amount of ETH to send to the paymaster", "0") .setAction(async (cmd, hre) => { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -57,23 +53,16 @@ task("deploy", "Deploys ZKsync SSO contracts") privateKey = LOCAL_RICH_WALLETS[0].privateKey; cmd.fund = "1"; } else { - if (!process.env.WALLET_PRIVATE_KEY) throw "⛔️ Wallet private key wasn't found in .env file!"; + if (!process.env.WALLET_PRIVATE_KEY) throw "Wallet private key wasn't found in .env file!"; privateKey = process.env.WALLET_PRIVATE_KEY; } const deployer = new Wallet(privateKey, provider); console.log("Deployer address:", deployer.address); - if (!cmd.only) { - await deploy("WebAuthValidator", deployer, !cmd.noProxy); - const sessions = await deploy(SESSIONS_NAME, deployer, !cmd.noProxy); - const implementation = await deploy(ACCOUNT_IMPL_NAME, deployer, false); - // TODO: enable proxy for factory -- currently it's not working - const factory = await deploy(AA_FACTORY_NAME, deployer, false, [implementation]); - const paymaster = await deploy(PAYMASTER_NAME, deployer, false, [factory, sessions]); - - if (cmd.fund != 0) { - console.log("Funding paymaster with", cmd.fund, "ETH..."); + async function fundPaymaster(paymaster: string, fund?: string | number) { + if (fund && fund != 0) { + console.log("Funding paymaster with", fund, "ETH..."); await ( await deployer.sendTransaction({ to: paymaster, @@ -84,35 +73,42 @@ task("deploy", "Deploys ZKsync SSO contracts") } else { console.log("--fund flag not provided, skipping funding paymaster\n"); } + } + + if (!cmd.only) { + await deploy(WEBAUTH_NAME, deployer, !cmd.noProxy); + const sessions = await deploy(SESSIONS_NAME, deployer, !cmd.noProxy); + const implementation = await deploy(ACCOUNT_IMPL_NAME, deployer, false); + const beacon = await deploy(BEACON_NAME, deployer, false, [implementation]); + const factory = await deploy(FACTORY_NAME, deployer, !cmd.noProxy, [beacon]); + const paymaster = await deploy(PAYMASTER_NAME, deployer, false, [factory, sessions]); + + await fundPaymaster(paymaster, cmd.fund); } else { let args: any[] = []; - if (cmd.only == AA_FACTORY_NAME) { + + if (cmd.only == BEACON_NAME) { if (!cmd.implementation) { - throw "⛔️ Implementation (--implementation ) address must be provided to deploy factory"; + throw "Account implementation (--implementation ) address must be provided to deploy beacon"; + } + args = [cmd.implementation]; + } + if (cmd.only == FACTORY_NAME) { + if (!cmd.implementation) { + throw "Beacon (--beacon ) address must be provided to deploy factory"; } args = [cmd.implementation]; } if (cmd.only == PAYMASTER_NAME) { if (!cmd.factory || !cmd.sessions) { - throw "⛔️ Factory (--factory ) and SessionModule (--sessions ) addresses must be provided to deploy paymaster"; + throw "Factory (--factory ) and SessionModule (--sessions ) addresses must be provided to deploy paymaster"; } args = [cmd.factory, cmd.sessions]; } const deployedContract = await deploy(cmd.only, deployer, false, args); if (cmd.only == PAYMASTER_NAME) { - if (cmd.fund == 0) { - console.log("⚠️ Paymaster is unfunded ⚠️\n"); - } else { - console.log("Funding paymaster with", cmd.fund, "ETH..."); - await ( - await deployer.sendTransaction({ - to: deployedContract, - value: ethers.parseEther(cmd.fund), - }) - ).wait(); - console.log("Paymaster funded\n"); - } + await fundPaymaster(deployedContract, cmd.fund); } } }); diff --git a/scripts/upgrade.ts b/scripts/upgrade.ts new file mode 100644 index 00000000..48b26e22 --- /dev/null +++ b/scripts/upgrade.ts @@ -0,0 +1,41 @@ +import { task } from "hardhat/config"; +import { Wallet } from "zksync-ethers"; +import { ethers } from "ethers"; + +// TODO: add support for constructor args +task("upgrade", "Upgrades ZKsync SSO contracts") + .addParam("proxy", "address of the proxy to upgrade") + .addParam("beaconAddress", "address of the beacon proxy for the upgrade") + .addPositionalParam("artifactName", "name of the artifact to upgrade to") + .setAction(async (cmd, hre) => { + const { LOCAL_RICH_WALLETS, getProvider, deployFactory, create2, ethersStaticSalt } = require("../test/utils"); + + let privateKey: string; + if (hre.network.name == "inMemoryNode" || hre.network.name == "dockerizedNode") { + console.log("Using local rich wallet"); + privateKey = LOCAL_RICH_WALLETS[0].privateKey; + cmd.fund = "1"; + } else { + if (!process.env.WALLET_PRIVATE_KEY) throw "Wallet private key wasn't found in .env file!"; + privateKey = process.env.WALLET_PRIVATE_KEY; + } + + const wallet = new Wallet(privateKey, getProvider()); + let newImpl; + + console.log("Deploying new implementation of", cmd.artifactName, "contract..."); + if (cmd.artifactName == "AAFactory") { + if (!cmd.beaconAddress) throw "Deploying the AAFactory requires a Beacon Address '--beacon-address '"; + newImpl = await deployFactory(wallet, cmd.beaconAddress); + } else { + newImpl = await create2(cmd.artifactName, wallet, ethersStaticSalt, []); + } + console.log("New implementation deployed at:", await newImpl.getAddress()); + + console.log("Upgrading proxy at", cmd.proxy, "to new implementation..."); + const abi = ["function upgradeTo(address newImplementation)"]; + const proxy = new ethers.Contract(cmd.proxy, abi, wallet); + const tx = await proxy.upgradeTo(await newImpl.getAddress()); + await tx.wait(); + console.log("Proxy upgraded successfully"); +}); diff --git a/src/AAFactory.sol b/src/AAFactory.sol index 4eef51fd..a7f41945 100644 --- a/src/AAFactory.sol +++ b/src/AAFactory.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import { DEPLOYER_SYSTEM_CONTRACT, IContractDeployer } from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; import { SystemContractsCaller } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; @@ -11,23 +10,29 @@ import { ISsoAccount } from "./interfaces/ISsoAccount.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @dev This contract is used to deploy SSO accounts as beacon proxies. -contract AAFactory is UpgradeableBeacon { +contract AAFactory { /// @notice Emitted when a new account is successfully created. /// @param accountAddress The address of the newly created account. /// @param uniqueAccountId A unique identifier for the account. event AccountCreated(address indexed accountAddress, string uniqueAccountId); /// @dev The bytecode hash of the beacon proxy, used for deploying proxy accounts. - bytes32 private immutable beaconProxyBytecodeHash; + bytes32 public immutable beaconProxyBytecodeHash; + address public immutable beacon; /// @notice A mapping from unique account IDs to their corresponding deployed account addresses. mapping(string => address) public accountMappings; /// @notice Constructor that initializes the factory with a beacon proxy bytecode hash and implementation contract address. /// @param _beaconProxyBytecodeHash The bytecode hash of the beacon proxy. - /// @param _implementation The address of the implementation contract used by the beacon. - constructor(bytes32 _beaconProxyBytecodeHash, address _implementation) UpgradeableBeacon(_implementation) { + /// @param _beacon The address of the UpgradeableBeacon contract used for the SSO accounts' beacon proxies. + constructor(bytes32 _beaconProxyBytecodeHash, address _beacon) { beaconProxyBytecodeHash = _beaconProxyBytecodeHash; + beacon = _beacon; + } + + function getEncodedBeacon() external view returns (bytes memory) { + return abi.encode(beacon); } /// @notice Deploys a new SSO account as a beacon proxy with the specified parameters. @@ -35,14 +40,12 @@ contract AAFactory is UpgradeableBeacon { /// @param _salt The salt used for the `create2` deployment to make the address deterministic. /// @param _uniqueAccountId A unique identifier for the new account. /// @param _initialValidators An array of initial validators for the new account. - /// @param _initialModules An array of initial modules to be added to the new account. /// @param _initialK1Owners An array of initial owners of the K1 key for the new account. /// @return accountAddress The address of the newly deployed SSO account. function deployProxySsoAccount( bytes32 _salt, string calldata _uniqueAccountId, bytes[] calldata _initialValidators, - bytes[] calldata _initialModules, address[] calldata _initialK1Owners ) external returns (address accountAddress) { require(accountMappings[_uniqueAccountId] == address(0), "Account already exists"); @@ -53,19 +56,14 @@ contract AAFactory is UpgradeableBeacon { uint128(0), abi.encodeCall( DEPLOYER_SYSTEM_CONTRACT.create2Account, - ( - _salt, - beaconProxyBytecodeHash, - abi.encode(address(this)), - IContractDeployer.AccountAbstractionVersion.Version1 - ) + (_salt, beaconProxyBytecodeHash, abi.encode(beacon), IContractDeployer.AccountAbstractionVersion.Version1) ) ); require(success, "Deployment failed"); (accountAddress) = abi.decode(returnData, (address)); - // Initialize the newly deployed account with validators, modules, and K1 owners. - ISsoAccount(accountAddress).initialize(_initialValidators, _initialModules, _initialK1Owners); + // Initialize the newly deployed account with validators, hooks and K1 owners. + ISsoAccount(accountAddress).initialize(_initialValidators, _initialK1Owners); accountMappings[_uniqueAccountId] = accountAddress; diff --git a/src/SsoAccount.sol b/src/SsoAccount.sol index 42b87573..382e1c1e 100644 --- a/src/SsoAccount.sol +++ b/src/SsoAccount.sol @@ -11,7 +11,6 @@ import { Utils } from "@matterlabs/zksync-contracts/l2/system-contracts/librarie import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { HookManager } from "./managers/HookManager.sol"; -import { ModuleManager } from "./managers/ModuleManager.sol"; import { TokenCallbackHandler, IERC165 } from "./helpers/TokenCallbackHandler.sol"; @@ -30,15 +29,7 @@ import { ISsoAccount } from "./interfaces/ISsoAccount.sol"; /// @notice This contract is a modular and extensible account implementation with support of /// multi-ownership, custom modules, validation/execution hooks and different signature validation formats. /// @dev Contract is expected to be used as Beacon proxy implementation. -contract SsoAccount is - Initializable, - HookManager, - ModuleManager, - ERC1271Handler, - TokenCallbackHandler, - BatchCaller, - ISsoAccount -{ +contract SsoAccount is Initializable, HookManager, ERC1271Handler, TokenCallbackHandler, BatchCaller, ISsoAccount { // Helper library for the Transaction struct using TransactionHelper for Transaction; @@ -50,22 +41,12 @@ contract SsoAccount is /// @dev Sets passkey and passkey validator within account storage /// @param _initialValidators An array of module validator addresses and initial validation keys /// in an ABI encoded format of `abi.encode(validatorAddr,validationKey))`. - /// @param _initialModules An array of native module addresses and their initialize data - /// in an ABI encoded format of `abi.encode(moduleAddr,initData))`. /// @param _initialK1Owners An array of addresses with full control over the account. - function initialize( - bytes[] calldata _initialValidators, - bytes[] calldata _initialModules, - address[] calldata _initialK1Owners - ) external initializer { + function initialize(bytes[] calldata _initialValidators, address[] calldata _initialK1Owners) external initializer { for (uint256 i = 0; i < _initialValidators.length; ++i) { (address validatorAddr, bytes memory validationKey) = abi.decode(_initialValidators[i], (address, bytes)); _addModuleValidator(validatorAddr, validationKey); } - for (uint256 i = 0; i < _initialModules.length; ++i) { - (address moduleAddr, bytes memory initData) = abi.decode(_initialModules[i], (address, bytes)); - _addNativeModule(moduleAddr, initData); - } for (uint256 i = 0; i < _initialK1Owners.length; ++i) { _k1AddOwner(_initialK1Owners[i]); } @@ -162,7 +143,6 @@ contract SsoAccount is emit FeePaid(); } - /// @notice This function is called by the system if the transaction has a paymaster /// and prepares the interaction with the paymaster. /// @param _transaction The transaction data. @@ -185,23 +165,24 @@ contract SsoAccount is /// @param _transaction The transaction data. /// @return The magic value if the validation was successful and bytes4(0) otherwise. function _validateTransaction(bytes32 _signedHash, Transaction calldata _transaction) internal returns (bytes4) { - if (_transaction.signature.length == 65) { - (address signer, ) = ECDSA.tryRecover(_signedHash, _transaction.signature); - return _k1IsOwner(signer) ? ACCOUNT_VALIDATION_SUCCESS_MAGIC : bytes4(0); - } - - // Extract the signature, validator address and hook data from the _transaction.signature - (bytes memory signature, address validator, bytes[] memory hookData) = SignatureDecoder.decodeSignature( - _transaction.signature - ); - // Run validation hooks - bool hookSuccess = runValidationHooks(_signedHash, _transaction, hookData); + bool hookSuccess = runValidationHooks(_signedHash, _transaction); if (!hookSuccess) { return bytes4(0); } - bool validationSuccess = _handleValidation(validator, _signedHash, signature); + if (_transaction.signature.length == 65) { + (address signer, ECDSA.RecoverError error) = ECDSA.tryRecover(_signedHash, _transaction.signature); + return + signer == address(0) || error != ECDSA.RecoverError.NoError || !_k1IsOwner(signer) + ? bytes4(0) + : ACCOUNT_VALIDATION_SUCCESS_MAGIC; + } + + // Extract the signature, validator address and hook data from the _transaction.signature + (bytes memory signature, address validator, ) = SignatureDecoder.decodeSignature(_transaction.signature); + + bool validationSuccess = _handleValidation(validator, _signedHash, signature, _transaction); return validationSuccess ? ACCOUNT_VALIDATION_SUCCESS_MAGIC : bytes4(0); } diff --git a/src/SsoBeacon.sol b/src/SsoBeacon.sol new file mode 100644 index 00000000..712304a3 --- /dev/null +++ b/src/SsoBeacon.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; + +/// @title SsoBeacon +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +contract SsoBeacon is UpgradeableBeacon { + constructor(address _implementation) UpgradeableBeacon(_implementation) {} +} diff --git a/src/auth/Auth.sol b/src/auth/Auth.sol index dd68419b..2681b4bb 100644 --- a/src/auth/Auth.sol +++ b/src/auth/Auth.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.24; import { BootloaderAuth } from "./BootloaderAuth.sol"; -import { ModuleAuth } from "./ModuleAuth.sol"; import { SelfAuth } from "./SelfAuth.sol"; import { HookAuth } from "./HookAuth.sol"; import { Errors } from "../libraries/Errors.sol"; @@ -12,11 +11,4 @@ import { Errors } from "../libraries/Errors.sol"; * @notice Abstract contract that organizes authentication logic for the contract * @author https://getclave.io */ -abstract contract Auth is BootloaderAuth, SelfAuth, ModuleAuth, HookAuth { - modifier onlySelfOrModule() { - if (msg.sender != address(this) && !_isModule(msg.sender)) { - revert Errors.NOT_FROM_SELF_OR_MODULE(); - } - _; - } -} +abstract contract Auth is BootloaderAuth, SelfAuth, HookAuth {} diff --git a/src/auth/ModuleAuth.sol b/src/auth/ModuleAuth.sol deleted file mode 100644 index e0b2b697..00000000 --- a/src/auth/ModuleAuth.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import { Errors } from "../libraries/Errors.sol"; - -/** - * @title ModuleAuth - * @notice Abstract contract that allows only calls from modules - * @author https://getclave.io - */ -abstract contract ModuleAuth { - function _isModule(address addr) internal view virtual returns (bool); - - modifier onlyModule() { - if (!_isModule(msg.sender)) { - revert Errors.NOT_FROM_MODULE(); - } - _; - } -} diff --git a/src/handlers/ERC1271Handler.sol b/src/handlers/ERC1271Handler.sol index 41bb5885..a8b4593f 100644 --- a/src/handlers/ERC1271Handler.sol +++ b/src/handlers/ERC1271Handler.sol @@ -2,8 +2,10 @@ pragma solidity ^0.8.24; import { IERC1271Upgradeable } from "@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol"; +import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; import { SignatureDecoder } from "../libraries/SignatureDecoder.sol"; +import { IModuleValidator } from "../interfaces/IModuleValidator.sol"; import { ValidationHandler } from "./ValidationHandler.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; @@ -34,9 +36,10 @@ abstract contract ERC1271Handler is IERC1271Upgradeable, EIP712("Sso1271", "1.0. bytes32 eip712Hash = _hashTypedDataV4(_ssoMessageHash(SsoMessage(signedHash))); - bool valid = _handleValidation(validator, eip712Hash, signature); + bool isValid = _isModuleValidator(validator) && + IModuleValidator(validator).validateSignature(eip712Hash, signature); - magicValue = valid ? _ERC1271_MAGIC : bytes4(0); + magicValue = isValid ? _ERC1271_MAGIC : bytes4(0); } /** diff --git a/src/handlers/ValidationHandler.sol b/src/handlers/ValidationHandler.sol index 757bb6de..276a7245 100644 --- a/src/handlers/ValidationHandler.sol +++ b/src/handlers/ValidationHandler.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; +import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + import { SignatureDecoder } from "../libraries/SignatureDecoder.sol"; import { BytesLinkedList } from "../libraries/LinkedList.sol"; import { OwnerManager } from "../managers/OwnerManager.sol"; import { ValidatorManager } from "../managers/ValidatorManager.sol"; -import { IK1Validator, IR1Validator } from "../interfaces/IValidator.sol"; import { IModuleValidator } from "../interfaces/IModuleValidator.sol"; /** @@ -18,34 +19,11 @@ abstract contract ValidationHandler is OwnerManager, ValidatorManager { function _handleValidation( address validator, bytes32 signedHash, - bytes memory signature - ) internal view returns (bool) { - if (_r1IsValidator(validator)) { - mapping(bytes => bytes) storage owners = OwnerManager._r1OwnersLinkedList(); - bytes memory cursor = owners[BytesLinkedList.SENTINEL_BYTES]; - while (cursor.length > BytesLinkedList.SENTINEL_LENGTH) { - bytes32[2] memory pubKey = abi.decode(cursor, (bytes32[2])); - - bool _success = IR1Validator(validator).validateSignature(signedHash, signature, pubKey); - - if (_success) { - return true; - } - - cursor = owners[cursor]; - } - } else if (_k1IsValidator(validator)) { - address recoveredAddress = IK1Validator(validator).validateSignature(signedHash, signature); - - if (recoveredAddress == address(0)) { - return false; - } - - if (OwnerManager._k1IsOwner(recoveredAddress)) { - return true; - } - } else if (_isModuleValidator(validator)) { - return IModuleValidator(validator).handleValidation(signedHash, signature); + bytes memory signature, + Transaction calldata transaction + ) internal returns (bool) { + if (_isModuleValidator(validator)) { + return IModuleValidator(validator).validateTransaction(signedHash, signature, transaction); } return false; diff --git a/src/helpers/TimestampAsserterLocator.sol b/src/helpers/TimestampAsserterLocator.sol index 68698a8e..f01a666b 100644 --- a/src/helpers/TimestampAsserterLocator.sol +++ b/src/helpers/TimestampAsserterLocator.sol @@ -9,7 +9,7 @@ library TimestampAsserterLocator { return ITimestampAsserter(address(0x00000000000000000000000000000000808012)); } if (block.chainid == 300) { - revert("Timestamp asserter is not deployed on ZKsync Sepolia testnet yet"); + return ITimestampAsserter(address(0xa64EC71Ee812ac62923c85cf0796aA58573c4Cf3)); } if (block.chainid == 324) { revert("Timestamp asserter is not deployed on ZKsync mainnet yet"); diff --git a/src/interfaces/IHook.sol b/src/interfaces/IHook.sol index c8be5785..564a2526 100644 --- a/src/interfaces/IHook.sol +++ b/src/interfaces/IHook.sol @@ -5,8 +5,11 @@ import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/li import { IInitable } from "../interfaces/IInitable.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +// Validation hooks are a non-standard way to always perform validation, +// They can't expect any specific transaction data or signature, but can be used to enforce +// additional restrictions on the account during the validation phase interface IValidationHook is IInitable, IERC165 { - function validationHook(bytes32 signedHash, Transaction calldata transaction, bytes calldata hookData) external; + function validationHook(bytes32 signedHash, Transaction calldata transaction) external; } interface IExecutionHook is IInitable, IERC165 { diff --git a/src/interfaces/IHookManager.sol b/src/interfaces/IHookManager.sol index 35952fa4..ff3485a7 100644 --- a/src/interfaces/IHookManager.sol +++ b/src/interfaces/IHookManager.sol @@ -20,7 +20,7 @@ interface IHookManager { /** * @notice Add a hook to the list of hooks and call it's init function - * @dev Can only be called by self or a module + * @dev Can only be called by self * @param hookAndData bytes calldata - Address of the hook and data to initialize it with * @param isValidation bool - True if the hook is a validation hook, false otherwise */ diff --git a/src/interfaces/IModule.sol b/src/interfaces/IModule.sol deleted file mode 100644 index 5cdb9487..00000000 --- a/src/interfaces/IModule.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import { IInitable } from "../interfaces/IInitable.sol"; -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IModule is IInitable, IERC165 {} diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol deleted file mode 100644 index f78ba289..00000000 --- a/src/interfaces/IModuleManager.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -/** - * @title Interface of the manager contract for modules - * @author https://getclave.io - */ -interface IModuleManager { - /** - * @notice Event emitted when a module is added - * @param module address - Address of the added module - */ - event AddModule(address indexed module); - - /** - * @notice Event emitted when a module is removed - * @param module address - Address of the removed module - */ - event RemoveModule(address indexed module); - - /** - * @notice Add a module to the list of modules and call it's init function - * @dev Can only be called by self or a module - * @param moduleAndData bytes calldata - Address of the module and data to initialize it with - */ - function addModule(bytes calldata moduleAndData) external; - - /** - * @notice Remove a module from the list of modules and call it's disable function - * @dev Can only be called by self or a module - * @param module address - Address of the module to remove - */ - function removeModule(address module) external; - - /** - * @notice Allow modules to execute arbitrary calls on behalf of the account - * @dev Can only be called by a module - * @param to address - Address to call - * @param value uint256 - Eth to send with call - * @param data bytes memory - Data to make the call with - */ - function executeFromModule(address to, uint256 value, bytes memory data) external; - - /** - * @notice Check if an address is in the list of modules - * @param addr address - Address to check - * @return bool - True if the address is a module, false otherwise - */ - function isModule(address addr) external returns (bool); - - /** - * @notice Get the list of modules - * @return moduleList address[] memory - List of modules - */ - function listModules() external view returns (address[] memory moduleList); -} diff --git a/src/interfaces/IModuleValidator.sol b/src/interfaces/IModuleValidator.sol index e576c27c..da749f79 100644 --- a/src/interfaces/IModuleValidator.sol +++ b/src/interfaces/IModuleValidator.sol @@ -1,12 +1,22 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; +import { IInitable } from "../interfaces/IInitable.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + /** * @title Modular validator interface for native AA * @dev Add signature to module or validate existing signatures for acccount */ -interface IModuleValidator { - function handleValidation(bytes32 signedHash, bytes memory signature) external view returns (bool); +interface IModuleValidator is IInitable, IERC165 { + function validateTransaction( + bytes32 signedHash, + bytes memory signature, + Transaction calldata transaction + ) external returns (bool); + + function validateSignature(bytes32 signedHash, bytes memory signature) external view returns (bool); function addValidationKey(bytes memory key) external returns (bool); } diff --git a/src/interfaces/IOwnerManager.sol b/src/interfaces/IOwnerManager.sol index 062ff2a5..4799fc02 100644 --- a/src/interfaces/IOwnerManager.sol +++ b/src/interfaces/IOwnerManager.sol @@ -6,43 +6,18 @@ pragma solidity ^0.8.24; * @author https://getclave.io */ interface IOwnerManager { - /** - * @notice Event emitted when a r1 owner is added - * @param pubKey bytes - r1 owner that has been added - */ - event R1AddOwner(bytes pubKey); - /** * @notice Event emitted when a k1 owner is added * @param addr address - k1 owner that has been added */ event K1AddOwner(address indexed addr); - /** - * @notice Event emitted when a r1 owner is removed - * @param pubKey bytes - r1 owner that has been removed - */ - event R1RemoveOwner(bytes pubKey); - /** * @notice Event emitted when a k1 owner is removed * @param addr address - k1 owner that has been removed */ event K1RemoveOwner(address indexed addr); - /** - * @notice Event emitted when all owners are cleared - */ - event ResetOwners(); - - /** - * @notice Adds a r1 owner to the list of r1 owners - * @dev Can only be called by self or a whitelisted module - * @dev Public Key length must be 64 bytes - * @param pubKey bytes calldata - Public key to add to the list of r1 owners - */ - function r1AddOwner(bytes calldata pubKey) external; - /** * @notice Adds a k1 owner to the list of k1 owners * @dev Can only be called by self or a whitelisted module @@ -51,14 +26,6 @@ interface IOwnerManager { */ function k1AddOwner(address addr) external; - /** - * @notice Removes a r1 owner from the list of r1 owners - * @dev Can only be called by self or a whitelisted module - * @dev Can not remove the last r1 owner - * @param pubKey bytes calldata - Public key to remove from the list of r1 owners - */ - function r1RemoveOwner(bytes calldata pubKey) external; - /** * @notice Removes a k1 owner from the list of k1 owners * @dev Can only be called by self or a whitelisted module @@ -66,21 +33,6 @@ interface IOwnerManager { */ function k1RemoveOwner(address addr) external; - /** - * @notice Clears both r1 owners and k1 owners and adds an r1 owner - * @dev Can only be called by self or a whitelisted module - * @dev Public Key length must be 64 bytes - * @param pubKey bytes calldata - new r1 owner to add - */ - function resetOwners(bytes calldata pubKey) external; - - /** - * @notice Checks if a public key is in the list of r1 owners - * @param pubKey bytes calldata - Public key to check - * @return bool - True if the public key is in the list, false otherwise - */ - function r1IsOwner(bytes calldata pubKey) external view returns (bool); - /** * @notice Checks if an address is in the list of k1 owners * @param addr address - Address to check @@ -88,12 +40,6 @@ interface IOwnerManager { */ function k1IsOwner(address addr) external view returns (bool); - /** - * @notice Returns the list of r1 owners - * @return r1OwnerList bytes[] memory - Array of r1 owner public keys - */ - function r1ListOwners() external view returns (bytes[] memory r1OwnerList); - /** * @notice Returns the list of k1 owners * @return k1OwnerList address[] memory - Array of k1 owner addresses diff --git a/src/interfaces/ISsoAccount.sol b/src/interfaces/ISsoAccount.sol index 45b2c08a..36ae812a 100644 --- a/src/interfaces/ISsoAccount.sol +++ b/src/interfaces/ISsoAccount.sol @@ -9,7 +9,6 @@ import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Rec import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import { IHookManager } from "./IHookManager.sol"; -import { IModuleManager } from "./IModuleManager.sol"; import { IOwnerManager } from "./IOwnerManager.sol"; import { IValidatorManager } from "./IValidatorManager.sol"; @@ -23,18 +22,11 @@ interface ISsoAccount is IERC721Receiver, IERC1155Receiver, IHookManager, - IModuleManager, IOwnerManager, IValidatorManager, IAccount { event FeePaid(); - // TODO: instead of splitting the modules by types here, we can just have a single array that checks the type of the module - // and installs it 7579 style - function initialize( - bytes[] calldata initialValidators, - bytes[] calldata initialModules, - address[] calldata k1Owners - ) external; + function initialize(bytes[] calldata initialValidators, address[] calldata k1Owners) external; } diff --git a/src/interfaces/IValidator.sol b/src/interfaces/IValidator.sol deleted file mode 100644 index db62cb72..00000000 --- a/src/interfaces/IValidator.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/** - * @title secp256r1 ec keys' signature validator interface - * @author https://getclave.io - */ -interface IR1Validator is IERC165 { - /** - * @notice Allows to validate secp256r1 ec signatures - * @param signedHash bytes32 - hash of the data that is signed by the key - * @param signature bytes - signature - * @param pubKey bytes32[2] - public key coordinates array for the x and y values - * @return valid bool - validation result - */ - function validateSignature( - bytes32 signedHash, - bytes calldata signature, - bytes32[2] calldata pubKey - ) external view returns (bool valid); - - /** - * @notice Allows to validate secp256r1 ec signatures - * @param messageHash bytes32 - message hash of the data that is signed by the key - * @param rs bytes32[2] - unwrapped challenge signature points (r & s) - * @param pubKey bytes32[2] - public key coordinates array for the x and y values - * @return valid bool - validation result - */ - function rawVerify( - bytes32 messageHash, - bytes32[2] calldata rs, - bytes32[2] calldata pubKey - ) external view returns (bool valid); -} - -/** - * @title secp256k1 ec keys' signature validator interface - * @author https://getclave.io - */ -interface IK1Validator is IERC165 { - /** - * @notice Allows to validate secp256k1 ec signatures - * @param signedHash bytes32 - hash of the transaction signed by the key - * @param signature bytes - signature - * @return signer address - recovered signer address - */ - function validateSignature(bytes32 signedHash, bytes calldata signature) external view returns (address signer); -} diff --git a/src/interfaces/IValidatorManager.sol b/src/interfaces/IValidatorManager.sol index c30c2e33..b324f9fd 100644 --- a/src/interfaces/IValidatorManager.sol +++ b/src/interfaces/IValidatorManager.sol @@ -6,49 +6,18 @@ pragma solidity ^0.8.24; * @author https://getclave.io */ interface IValidatorManager { - /** - * @notice Event emitted when a r1 validator is added - * @param validator address - Address of the added r1 validator - */ - event R1AddValidator(address indexed validator); - - /** - * @notice Event emitted when a k1 validator is added - * @param validator address - Address of the added k1 validator - */ - event K1AddValidator(address indexed validator); - /** * @notice Event emitted when a modular validator is added * @param validator address - Address of the added modular validator */ event AddModuleValidator(address indexed validator); - /** - * @notice Event emitted when a r1 validator is removed - * @param validator address - Address of the removed r1 validator - */ - event R1RemoveValidator(address indexed validator); - - /** - * @notice Event emitted when a k1 validator is removed - * @param validator address - Address of the removed k1 validator - */ - event K1RemoveValidator(address indexed validator); - /** * @notice Event emitted when a modular validator is removed * @param validator address - Address of the removed modular validator */ event RemoveModuleValidator(address indexed validator); - /** - * @notice Adds a validator to the list of r1 validators - * @dev Can only be called by self or a whitelisted module - * @param validator address - Address of the r1 validator to add - */ - function r1AddValidator(address validator) external; - /** * @notice Adds a validator to the list of modular validators * @dev Can only be called by self or a whitelisted module @@ -57,28 +26,6 @@ interface IValidatorManager { */ function addModuleValidator(address validator, bytes memory accountValidationKey) external; - /** - * @notice Adds a validator to the list of k1 validators - * @dev Can only be called by self or a whitelisted module - * @param validator address - Address of the k1 validator to add - */ - function k1AddValidator(address validator) external; - - /** - * @notice Removes a validator from the list of r1 validators - * @dev Can only be called by self or a whitelisted module - * @dev Can not remove the last validator - * @param validator address - Address of the validator to remove - */ - function r1RemoveValidator(address validator) external; - - /** - * @notice Removes a validator from the list of k1 validators - * @dev Can only be called by self or a whitelisted module - * @param validator address - Address of the validator to remove - */ - function k1RemoveValidator(address validator) external; - /** * @notice Removes a validator from the list of modular validators * @dev Can only be called by self or a whitelisted module @@ -86,20 +33,6 @@ interface IValidatorManager { */ function removeModuleValidator(address validator) external; - /** - * @notice Checks if an address is in the r1 validator list - * @param validator address -Address of the validator to check - * @return True if the address is a validator, false otherwise - */ - function r1IsValidator(address validator) external view returns (bool); - - /** - * @notice Checks if an address is in the k1 validator list - * @param validator address - Address of the validator to check - * @return True if the address is a validator, false otherwise - */ - function k1IsValidator(address validator) external view returns (bool); - /** * @notice Checks if an address is in the modular validator list * @param validator address - Address of the validator to check @@ -107,18 +40,6 @@ interface IValidatorManager { */ function isModuleValidator(address validator) external view returns (bool); - /** - * @notice Returns the list of r1 validators - * @return validatorList address[] memory - Array of r1 validator addresses - */ - function r1ListValidators() external view returns (address[] memory validatorList); - - /** - * @notice Returns the list of k1 validators - * @return validatorList address[] memory - Array of k1 validator addresses - */ - function k1ListValidators() external view returns (address[] memory validatorList); - /** * @notice Returns the list of modular validators * @return validatorList address[] memory - Array of modular validator addresses diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index ce2a4acd..2b2e61d4 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -28,14 +28,12 @@ library Errors { OWNER MANAGER //////////////////////////////////////////////////////////////*/ - error EMPTY_R1_OWNERS(); error INVALID_PUBKEY_LENGTH(); /*////////////////////////////////////////////////////////////// VALIDATOR MANAGER //////////////////////////////////////////////////////////////*/ - error EMPTY_R1_VALIDATORS(); error VALIDATOR_ERC165_FAIL(); /*////////////////////////////////////////////////////////////// diff --git a/src/libraries/SessionLib.sol b/src/libraries/SessionLib.sol index 7bcf1a8a..2303e329 100644 --- a/src/libraries/SessionLib.sol +++ b/src/libraries/SessionLib.sol @@ -162,6 +162,30 @@ library SessionLib { constraint.limit.checkAndUpdate(tracker, uint256(param), period); } + function validateFeeLimit( + SessionStorage storage state, + Transaction calldata transaction, + SessionSpec memory spec, + uint64 periodId + ) internal { + // This is split from `validate` to prevent gas estimation failures. + // When this check was part of `validate`, gas estimation could fail due to + // fee limit being smaller than the upper bound of the gas estimation binary search. + // By splitting this check, we can now have this order of operations in `validateTransaction`: + // 1. session.validate() + // 2. ECDSA.tryRecover() + // 3. session.validateFeeLimit() + // This way, gas estimation will exit on step 2 instead of failing, but will still run through + // most of the computation needed to validate the session. + + // TODO: update fee allowance with the gasleft/refund at the end of execution + if (transaction.paymaster != 0) { + // If a paymaster is paying the fee, we don't need to check the fee limit + uint256 fee = transaction.maxFeePerGas * transaction.gasLimit; + spec.feeLimit.checkAndUpdate(state.fee, fee, periodId); + } + } + function validate( SessionStorage storage state, Transaction calldata transaction, @@ -179,10 +203,6 @@ library SessionLib { require(state.status[msg.sender] == Status.Active, "Session is not active"); TimestampAsserterLocator.locate().assertTimestampInRange(0, spec.expiresAt); - // TODO: update fee allowance with the gasleft/refund at the end of execution - uint256 fee = transaction.maxFeePerGas * transaction.gasLimit; - spec.feeLimit.checkAndUpdate(state.fee, fee, periodIds[0]); - address target = address(uint160(transaction.to)); if (transaction.paymasterInput.length >= 4) { diff --git a/src/libraries/SignatureDecoder.sol b/src/libraries/SignatureDecoder.sol index ea48fdae..bc52026f 100644 --- a/src/libraries/SignatureDecoder.sol +++ b/src/libraries/SignatureDecoder.sol @@ -7,8 +7,8 @@ library SignatureDecoder { // Decode transaction.signature into signature, validator and hook data function decodeSignature( bytes calldata txSignature - ) internal pure returns (bytes memory signature, address validator, bytes[] memory hookData) { - (signature, validator, hookData) = abi.decode(txSignature, (bytes, address, bytes[])); + ) internal pure returns (bytes memory signature, address validator, bytes memory validatorData) { + (signature, validator, validatorData) = abi.decode(txSignature, (bytes, address, bytes)); } // Decode signature into signature and validator diff --git a/src/libraries/SsoStorage.sol b/src/libraries/SsoStorage.sol index e98e81b8..a089ff8d 100644 --- a/src/libraries/SsoStorage.sol +++ b/src/libraries/SsoStorage.sol @@ -7,33 +7,16 @@ library SsoStorage { struct Layout { // ┌───────────────────┐ // │ Ownership Data │ - mapping(bytes => bytes) r1Owners; mapping(address => address) k1Owners; uint256[50] __gap_0; // └───────────────────┘ - // ┌───────────────────┐ - // │ Fallback │ - address defaultFallbackContract; // for next version - mapping(bytes4 selector => address) fallbackContractBySelector; - uint256[50] __gap_1; - // └───────────────────┘ - // ┌───────────────────┐ // │ Validation │ - mapping(address => address) r1Validators; - mapping(address => address) k1Validators; mapping(address => address) moduleValidators; uint256[50] __gap_2; // └───────────────────┘ - // ┌───────────────────┐ - // │ Module │ - mapping(address => address) modules; - mapping(address => address) execModules; - uint256[50] __gap_3; - // └───────────────────┘ - // ┌───────────────────┐ // │ Hooks │ mapping(address => address) validationHooks; diff --git a/src/managers/HookManager.sol b/src/managers/HookManager.sol index 23717a62..44db44b0 100644 --- a/src/managers/HookManager.sol +++ b/src/managers/HookManager.sol @@ -71,12 +71,12 @@ abstract contract HookManager is IHookManager, Auth { } /// @inheritdoc IHookManager - function addHook(bytes calldata hookAndData, bool isValidation) external override onlySelfOrModule { + function addHook(bytes calldata hookAndData, bool isValidation) external override onlySelf { _addHook(hookAndData, isValidation); } /// @inheritdoc IHookManager - function removeHook(address hook, bool isValidation) external override onlySelfOrModule { + function removeHook(address hook, bool isValidation) external override onlySelf { _removeHook(hook, isValidation); } @@ -109,23 +109,13 @@ abstract contract HookManager is IHookManager, Auth { } // Runs the validation hooks that are enabled by the account and returns true if none reverts - function runValidationHooks( - bytes32 signedHash, - Transaction calldata transaction, - bytes[] memory hookData - ) internal returns (bool) { + function runValidationHooks(bytes32 signedHash, Transaction calldata transaction) internal returns (bool) { mapping(address => address) storage validationHooks = _validationHooksLinkedList(); address cursor = validationHooks[AddressLinkedList.SENTINEL_ADDRESS]; - uint256 idx; // Iterate through hooks while (cursor > AddressLinkedList.SENTINEL_ADDRESS) { - // Call it with corresponding hookData - bool success = _call( - cursor, - abi.encodeCall(IValidationHook.validationHook, (signedHash, transaction, hookData[idx])) - ); - ++idx; + bool success = _call(cursor, abi.encodeCall(IValidationHook.validationHook, (signedHash, transaction))); if (!success) { return false; @@ -134,9 +124,6 @@ abstract contract HookManager is IHookManager, Auth { cursor = validationHooks[cursor]; } - // Ensure that hookData is not tampered with - if (hookData.length != idx) return false; - return true; } @@ -179,12 +166,16 @@ abstract contract HookManager is IHookManager, Auth { address hookAddress = address(bytes20(hookAndData[0:20])); + bytes calldata initData = hookAndData[20:]; + + _installHook(hookAddress, initData, isValidation); + } + + function _installHook(address hookAddress, bytes memory initData, bool isValidation) internal { if (!_supportsHook(hookAddress, isValidation)) { revert Errors.HOOK_ERC165_FAIL(); } - bytes calldata initData = hookAndData[20:]; - if (isValidation) { _validationHooksLinkedList().add(hookAddress); } else { @@ -203,8 +194,7 @@ abstract contract HookManager is IHookManager, Auth { _executionHooksLinkedList().remove(hook); } - (bool success, ) = hook.excessivelySafeCall(gasleft(), 0, abi.encodeWithSelector(IInitable.disable.selector)); - (success); // silence unused local variable warning + hook.excessivelySafeCall(gasleft(), 0, abi.encodeWithSelector(IInitable.disable.selector)); emit RemoveHook(hook); } diff --git a/src/managers/ModuleManager.sol b/src/managers/ModuleManager.sol deleted file mode 100644 index 96cca418..00000000 --- a/src/managers/ModuleManager.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.24; - -import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import { ExcessivelySafeCall } from "@nomad-xyz/excessively-safe-call/src/ExcessivelySafeCall.sol"; - -import { SsoStorage } from "../libraries/SsoStorage.sol"; -import { Auth } from "../auth/Auth.sol"; -import { AddressLinkedList } from "../libraries/LinkedList.sol"; -import { Errors } from "../libraries/Errors.sol"; -import { IModule } from "../interfaces/IModule.sol"; -import { IInitable } from "../interfaces/IInitable.sol"; -import { ISsoAccount } from "../interfaces/ISsoAccount.sol"; -import { IModuleManager } from "../interfaces/IModuleManager.sol"; - -/** - * @title Manager contract for modules - * @notice Abstract contract for managing the enabled modules of the account - * @dev Module addresses are stored in a linked list - * @author https://getclave.io - */ -abstract contract ModuleManager is IModuleManager, Auth { - // Helper library for address to address mappings - using AddressLinkedList for mapping(address => address); - // Interface helper library - using ERC165Checker for address; - // Low level calls helper library - using ExcessivelySafeCall for address; - - /// @inheritdoc IModuleManager - function addModule(bytes calldata moduleAndData) external override onlySelfOrModule { - _addModule(moduleAndData); - } - - /// @inheritdoc IModuleManager - function removeModule(address module) external override onlySelfOrModule { - _removeModule(module); - } - - /// @inheritdoc IModuleManager - function executeFromModule(address to, uint256 value, bytes memory data) external override onlyModule { - if (to == address(this)) revert Errors.RECURSIVE_MODULE_CALL(); - - assembly { - let result := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) - if iszero(result) { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - } - } - - /// @inheritdoc IModuleManager - function isModule(address addr) external view override returns (bool) { - return _isModule(addr); - } - - /// @inheritdoc IModuleManager - function listModules() external view override returns (address[] memory moduleList) { - moduleList = _modulesLinkedList().list(); - } - - function _addNativeModule(address moduleAddress, bytes memory moduleData) internal { - if (!_supportsModule(moduleAddress)) { - revert Errors.MODULE_ERC165_FAIL(); - } - - _modulesLinkedList().add(moduleAddress); - - IModule(moduleAddress).init(moduleData); - - emit AddModule(moduleAddress); - } - - function _addModule(bytes calldata moduleAndData) internal { - if (moduleAndData.length < 20) { - revert Errors.EMPTY_MODULE_ADDRESS(); - } - - address moduleAddress = address(bytes20(moduleAndData[0:20])); - bytes calldata initData = moduleAndData[20:]; - - if (!_supportsModule(moduleAddress)) { - revert Errors.MODULE_ERC165_FAIL(); - } - - _modulesLinkedList().add(moduleAddress); - - IModule(moduleAddress).init(initData); - - emit AddModule(moduleAddress); - } - - function _addExternalExecutorPermission(address module, bytes calldata data) internal virtual { - _externalExecutorModule().add(module); - - emit AddModule(module); - } - - function _addFallbackModule(address module, bytes calldata data) internal virtual { - SsoStorage.layout().fallbackContractBySelector[bytes4(data[0:4])] = module; - - emit AddModule(module); - } - - function _removeFallbackModule(address module, bytes calldata data) internal virtual { - SsoStorage.layout().fallbackContractBySelector[bytes4(data[0:4])] = address(0); - - emit RemoveModule(module); - } - - function _removeModule(address module) internal { - _modulesLinkedList().remove(module); - - (bool success, ) = module.excessivelySafeCall(gasleft(), 0, abi.encodeWithSelector(IInitable.disable.selector)); - (success); // silence unused local variable warning - - emit RemoveModule(module); - } - - function _isModule(address addr) internal view override returns (bool) { - return _modulesLinkedList().exists(addr); - } - - function _modulesLinkedList() private view returns (mapping(address => address) storage modules) { - modules = SsoStorage.layout().modules; - } - - function _externalExecutorModule() private view returns (mapping(address => address) storage modules) { - modules = SsoStorage.layout().execModules; - } - - function _removeExternalExecutorModule(address module, bytes calldata data) internal { - _externalExecutorModule().remove(module); - - emit RemoveModule(module); - } - - function _supportsModule(address module) internal view returns (bool) { - // this is pretty dumb, since type(IModule).interfaceId is 0x00000000, but is correct as per ERC165 - // context: https://github.com/ethereum/solidity/issues/7856#issuecomment-585337461 - return module.supportsInterface(type(IModule).interfaceId); - } -} diff --git a/src/managers/OwnerManager.sol b/src/managers/OwnerManager.sol index fa5a764c..99402e26 100644 --- a/src/managers/OwnerManager.sol +++ b/src/managers/OwnerManager.sol @@ -11,7 +11,6 @@ import { IOwnerManager } from "../interfaces/IOwnerManager.sol"; /** * @title Manager contract for owners * @notice Abstract contract for managing the owners of the account - * @dev R1 Owners are 64 byte secp256r1 public keys * @dev K1 Owners are secp256k1 addresses * @dev Owners are stored in a linked list * @author https://getclave.io @@ -23,107 +22,45 @@ abstract contract OwnerManager is IOwnerManager, Auth { using AddressLinkedList for mapping(address => address); /// @inheritdoc IOwnerManager - function r1AddOwner(bytes calldata pubKey) external override onlySelfOrModule { - _r1AddOwner(pubKey); - } - - /// @inheritdoc IOwnerManager - function k1AddOwner(address addr) external override onlySelfOrModule { + function k1AddOwner(address addr) external override onlySelf { _k1AddOwner(addr); } /// @inheritdoc IOwnerManager - function r1RemoveOwner(bytes calldata pubKey) external override onlySelfOrModule { - _r1RemoveOwner(pubKey); - } - - /// @inheritdoc IOwnerManager - function k1RemoveOwner(address addr) external override onlySelfOrModule { + function k1RemoveOwner(address addr) external override onlySelf { _k1RemoveOwner(addr); } - /// @inheritdoc IOwnerManager - function resetOwners(bytes calldata pubKey) external override onlySelfOrModule { - _r1ClearOwners(); - _k1ClearOwners(); - - emit ResetOwners(); - - _r1AddOwner(pubKey); - } - - /// @inheritdoc IOwnerManager - function r1IsOwner(bytes calldata pubKey) external view override returns (bool) { - return _r1IsOwner(pubKey); - } - /// @inheritdoc IOwnerManager function k1IsOwner(address addr) external view override returns (bool) { return _k1IsOwner(addr); } - /// @inheritdoc IOwnerManager - function r1ListOwners() external view override returns (bytes[] memory r1OwnerList) { - r1OwnerList = _r1OwnersLinkedList().list(); - } - /// @inheritdoc IOwnerManager function k1ListOwners() external view override returns (address[] memory k1OwnerList) { k1OwnerList = _k1OwnersLinkedList().list(); } - function _r1AddOwner(bytes calldata pubKey) internal { - if (pubKey.length != 64) { - revert Errors.INVALID_PUBKEY_LENGTH(); - } - - _r1OwnersLinkedList().add(pubKey); - - emit R1AddOwner(pubKey); - } - function _k1AddOwner(address addr) internal { _k1OwnersLinkedList().add(addr); emit K1AddOwner(addr); } - function _r1RemoveOwner(bytes calldata pubKey) internal { - _r1OwnersLinkedList().remove(pubKey); - - if (_r1OwnersLinkedList().isEmpty()) { - revert Errors.EMPTY_R1_OWNERS(); - } - - emit R1RemoveOwner(pubKey); - } - function _k1RemoveOwner(address addr) internal { _k1OwnersLinkedList().remove(addr); emit K1RemoveOwner(addr); } - function _r1IsOwner(bytes calldata pubKey) internal view returns (bool) { - return _r1OwnersLinkedList().exists(pubKey); - } - function _k1IsOwner(address addr) internal view returns (bool) { return _k1OwnersLinkedList().exists(addr); } - function _r1OwnersLinkedList() internal view returns (mapping(bytes => bytes) storage r1Owners) { - r1Owners = SsoStorage.layout().r1Owners; - } - function _k1OwnersLinkedList() internal view returns (mapping(address => address) storage k1Owners) { k1Owners = SsoStorage.layout().k1Owners; } - function _r1ClearOwners() private { - _r1OwnersLinkedList().clear(); - } - function _k1ClearOwners() private { _k1OwnersLinkedList().clear(); } diff --git a/src/managers/ValidatorManager.sol b/src/managers/ValidatorManager.sol index 94c8f540..99b4fca7 100644 --- a/src/managers/ValidatorManager.sol +++ b/src/managers/ValidatorManager.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.24; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import { ExcessivelySafeCall } from "@nomad-xyz/excessively-safe-call/src/ExcessivelySafeCall.sol"; import { Auth } from "../auth/Auth.sol"; import { Errors } from "../libraries/Errors.sol"; import { SsoStorage } from "../libraries/SsoStorage.sol"; +import { IInitable } from "../interfaces/IInitable.sol"; import { AddressLinkedList } from "../libraries/LinkedList.sol"; -import { IR1Validator, IK1Validator } from "../interfaces/IValidator.sol"; import { IValidatorManager } from "../interfaces/IValidatorManager.sol"; import { IModuleValidator } from "../interfaces/IModuleValidator.sol"; @@ -22,152 +23,56 @@ abstract contract ValidatorManager is IValidatorManager, Auth { using AddressLinkedList for mapping(address => address); // Interface helper library using ERC165Checker for address; + // Low level calls helper library + using ExcessivelySafeCall for address; - /// @inheritdoc IValidatorManager - function r1AddValidator(address validator) external override onlySelfOrModule { - _r1AddValidator(validator); - } - - function addModuleValidator(address validator, bytes memory initialAccountValidationKey) external onlySelfOrModule { + function addModuleValidator(address validator, bytes memory initialAccountValidationKey) external onlySelf { _addModuleValidator(validator, initialAccountValidationKey); } - /// @inheritdoc IValidatorManager - function k1AddValidator(address validator) external override onlySelfOrModule { - _k1AddValidator(validator); - } - - /// @inheritdoc IValidatorManager - function r1RemoveValidator(address validator) external override onlySelfOrModule { - _r1RemoveValidator(validator); - } - - /// @inheritdoc IValidatorManager - function k1RemoveValidator(address validator) external override onlySelfOrModule { - _k1RemoveValidator(validator); - } - ///@inheritdoc IValidatorManager - function removeModuleValidator(address validator) external onlySelfOrModule { + function removeModuleValidator(address validator) external onlySelf { _removeModuleValidator(validator); } - /// @inheritdoc IValidatorManager - function r1IsValidator(address validator) external view override returns (bool) { - return _r1IsValidator(validator); - } - - /// @inheritdoc IValidatorManager - function k1IsValidator(address validator) external view override returns (bool) { - return _k1IsValidator(validator); - } - /// @inheritdoc IValidatorManager function isModuleValidator(address validator) external view override returns (bool) { return _isModuleValidator(validator); } - /// @inheritdoc IValidatorManager - function r1ListValidators() external view override returns (address[] memory validatorList) { - validatorList = _r1ValidatorsLinkedList().list(); - } - - /// @inheritdoc IValidatorManager - function k1ListValidators() external view override returns (address[] memory validatorList) { - validatorList = _k1ValidatorsLinkedList().list(); - } - /// @inheritdoc IValidatorManager function listModuleValidators() external view override returns (address[] memory validatorList) { validatorList = _moduleValidatorsLinkedList().list(); } - function _r1AddValidator(address validator) internal { - if (!_supportsR1(validator)) { - revert Errors.VALIDATOR_ERC165_FAIL(); - } - - _r1ValidatorsLinkedList().add(validator); - - emit R1AddValidator(validator); - } - function _addModuleValidator(address validator, bytes memory accountValidationKey) internal { if (!_supportsModuleValidator(validator)) { revert Errors.VALIDATOR_ERC165_FAIL(); } _moduleValidatorsLinkedList().add(validator); - IModuleValidator(validator).addValidationKey(accountValidationKey); + IModuleValidator(validator).init(accountValidationKey); emit AddModuleValidator(validator); } - function _k1AddValidator(address validator) internal { - if (!_supportsK1(validator)) { - revert Errors.VALIDATOR_ERC165_FAIL(); - } - - _k1ValidatorsLinkedList().add(validator); - - emit K1AddValidator(validator); - } - - function _r1RemoveValidator(address validator) internal { - _r1ValidatorsLinkedList().remove(validator); - - if (_r1ValidatorsLinkedList().isEmpty()) { - revert Errors.EMPTY_R1_VALIDATORS(); - } - - emit R1RemoveValidator(validator); - } - - function _k1RemoveValidator(address validator) internal { - _k1ValidatorsLinkedList().remove(validator); - - emit K1RemoveValidator(validator); - } - function _removeModuleValidator(address validator) internal { _moduleValidatorsLinkedList().remove(validator); - emit RemoveModuleValidator(validator); - } + validator.excessivelySafeCall(gasleft(), 0, abi.encodeWithSelector(IInitable.disable.selector)); - function _r1IsValidator(address validator) internal view returns (bool) { - return _r1ValidatorsLinkedList().exists(validator); + emit RemoveModuleValidator(validator); } function _isModuleValidator(address validator) internal view returns (bool) { return _moduleValidatorsLinkedList().exists(validator); } - function _k1IsValidator(address validator) internal view returns (bool) { - return _k1ValidatorsLinkedList().exists(validator); - } - - function _supportsR1(address validator) internal view returns (bool) { - return validator.supportsInterface(type(IR1Validator).interfaceId); - } - - function _supportsK1(address validator) internal view returns (bool) { - return validator.supportsInterface(type(IK1Validator).interfaceId); - } - function _supportsModuleValidator(address validator) internal view returns (bool) { return validator.supportsInterface(type(IModuleValidator).interfaceId); } - function _r1ValidatorsLinkedList() private view returns (mapping(address => address) storage r1Validators) { - r1Validators = SsoStorage.layout().r1Validators; - } - function _moduleValidatorsLinkedList() private view returns (mapping(address => address) storage moduleValidators) { moduleValidators = SsoStorage.layout().moduleValidators; } - - function _k1ValidatorsLinkedList() private view returns (mapping(address => address) storage k1Validators) { - k1Validators = SsoStorage.layout().k1Validators; - } } diff --git a/src/validators/SessionKeyValidator.sol b/src/validators/SessionKeyValidator.sol index 074b5178..60ff8c79 100644 --- a/src/validators/SessionKeyValidator.sol +++ b/src/validators/SessionKeyValidator.sol @@ -4,18 +4,16 @@ pragma solidity ^0.8.24; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { IModule } from "../interfaces/IModule.sol"; -import { IValidationHook } from "../interfaces/IHook.sol"; import { IModuleValidator } from "../interfaces/IModuleValidator.sol"; import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import { IHookManager } from "../interfaces/IHookManager.sol"; import { IValidatorManager } from "../interfaces/IValidatorManager.sol"; import { SessionLib } from "../libraries/SessionLib.sol"; +import { SignatureDecoder } from "../libraries/SignatureDecoder.sol"; -contract SessionKeyValidator is IValidationHook, IModuleValidator, IModule { +contract SessionKeyValidator is IModuleValidator { using SessionLib for SessionLib.SessionStorage; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -41,30 +39,19 @@ contract SessionKeyValidator is IValidationHook, IModuleValidator, IModule { return sessions[sessionHash].status[account]; } - function handleValidation(bytes32 signedHash, bytes memory signature) external view returns (bool) { - // This only succeeds if the validationHook has previously succeeded for this hash. - uint256 slot = uint256(signedHash); - uint256 hookResult; - assembly { - hookResult := tload(slot) - } - require(hookResult == 1, "Can't call this function without calling validationHook"); - return true; + // This module should not be used to validate signatures + function validateSignature(bytes32 signedHash, bytes memory signature) external view returns (bool) { + return false; } function addValidationKey(bytes memory sessionData) external returns (bool) { - if (sessionData.length == 0) { - return false; - } - SessionLib.SessionSpec memory sessionSpec = abi.decode(sessionData, (SessionLib.SessionSpec)); - createSession(sessionSpec); - return true; + return _addValidationKey(sessionData); } function createSession(SessionLib.SessionSpec memory sessionSpec) public { bytes32 sessionHash = keccak256(abi.encode(sessionSpec)); - require(_isInitialized(msg.sender), "Account not initialized"); - require(sessionSpec.signer != address(0), "Invalid signer"); + require(isInitialized(msg.sender), "Account not initialized"); + require(sessionSpec.signer != address(0), "Invalid signer (create)"); require(sessions[sessionHash].status[msg.sender] == SessionLib.Status.NotInitialized, "Session already exists"); require(sessionSpec.feeLimit.limitType != SessionLib.LimitType.Unlimited, "Unlimited fee allowance is not safe"); sessionCounter[msg.sender]++; @@ -73,32 +60,32 @@ contract SessionKeyValidator is IValidationHook, IModuleValidator, IModule { } function init(bytes calldata data) external { - // to prevent recursion, since addHook also calls init - if (!_isInitialized(msg.sender)) { - IHookManager(msg.sender).addHook(abi.encodePacked(address(this)), true); - IValidatorManager(msg.sender).addModuleValidator(address(this), data); + if (data.length != 0) { + require(_addValidationKey(data), "Init failed"); } } + function _addValidationKey(bytes memory sessionData) internal returns (bool) { + SessionLib.SessionSpec memory sessionSpec = abi.decode(sessionData, (SessionLib.SessionSpec)); + createSession(sessionSpec); + return true; + } + function disable() external { - if (_isInitialized(msg.sender)) { + if (isInitialized(msg.sender)) { // Here we have to revoke all keys, so that if the module // is installed again later, there will be no active sessions from the past. // Problem: if there are too many keys, this will run out of gas. // Solution: before uninstalling, require that all keys are revoked manually. require(sessionCounter[msg.sender] == 0, "Revoke all keys first"); IValidatorManager(msg.sender).removeModuleValidator(address(this)); - IHookManager(msg.sender).removeHook(address(this), true); } } function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { return interfaceId != 0xffffffff && - (interfaceId == type(IERC165).interfaceId || - interfaceId == type(IValidationHook).interfaceId || - interfaceId == type(IModuleValidator).interfaceId || - interfaceId == type(IModule).interfaceId); + (interfaceId == type(IERC165).interfaceId || interfaceId == type(IModuleValidator).interfaceId); } // TODO: make the session owner able revoke its own key, in case it was leaked, to prevent further misuse? @@ -120,34 +107,33 @@ contract SessionKeyValidator is IValidationHook, IModuleValidator, IModule { * @param smartAccount The smart account to check * @return true if validator is registered for the account, false otherwise */ - function isInitialized(address smartAccount) external view returns (bool) { - return _isInitialized(smartAccount); - } - - function _isInitialized(address smartAccount) internal view returns (bool) { - return IHookManager(smartAccount).isHook(address(this)); + function isInitialized(address smartAccount) public view returns (bool) { + return IValidatorManager(smartAccount).isModuleValidator(address(this)); } - function validationHook(bytes32 signedHash, Transaction calldata transaction, bytes calldata hookData) external { - (bytes memory signature, address validator, ) = abi.decode(transaction.signature, (bytes, address, bytes[])); - if (validator != address(this)) { - // This transaction is not meant to be validated by this module - return; - } + function validateTransaction( + bytes32 signedHash, + bytes memory _signature, + Transaction calldata transaction + ) external returns (bool) { + (bytes memory transactionSignature, address validator, bytes memory validatorData) = SignatureDecoder + .decodeSignature(transaction.signature); (SessionLib.SessionSpec memory spec, uint64[] memory periodIds) = abi.decode( - hookData, + validatorData, // this is passed by the signature builder (SessionLib.SessionSpec, uint64[]) ); - (address recoveredAddress, ) = ECDSA.tryRecover(signedHash, signature); - require(recoveredAddress == spec.signer, "Invalid signer"); + require(spec.signer != address(0), "Invalid signer (empty)"); bytes32 sessionHash = keccak256(abi.encode(spec)); + // this generally throws instead of returning false sessions[sessionHash].validate(transaction, spec, periodIds); - - // Set the validation result to 1 for this hash, so that isValidSignature succeeds - uint256 slot = uint256(signedHash); - assembly { - tstore(slot, 1) + (address recoveredAddress, ECDSA.RecoverError recoverError) = ECDSA.tryRecover(signedHash, transactionSignature); + if (recoverError != ECDSA.RecoverError.NoError || recoveredAddress == address(0)) { + return false; } + require(recoveredAddress == spec.signer, "Invalid signer (mismatch)"); + // This check is separate and performed last to prevent gas estimation failures + sessions[sessionHash].validateFeeLimit(transaction, spec, periodIds[0]); + return true; } /** diff --git a/src/validators/WebAuthValidator.sol b/src/validators/WebAuthValidator.sol index a5cd9feb..c2e24c09 100644 --- a/src/validators/WebAuthValidator.sol +++ b/src/validators/WebAuthValidator.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.24; +import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + import { IModuleValidator } from "../interfaces/IModuleValidator.sol"; import { VerifierCaller } from "../helpers/VerifierCaller.sol"; import { JsmnSolLib } from "../libraries/JsmnSolLib.sol"; @@ -21,7 +24,23 @@ contract WebAuthValidator is VerifierCaller, IModuleValidator { mapping(string originDomain => mapping(address accountAddress => bytes32)) public lowerKeyHalf; mapping(string originDomain => mapping(address accountAddress => bytes32)) public upperKeyHalf; + function init(bytes calldata key) external { + require(_addValidationKey(key), "failed to init"); + } + function addValidationKey(bytes memory key) external returns (bool) { + return _addValidationKey(key); + } + + // There's no mapping from account address to domains, + // so there's no way to just delete all the keys + // We can only disconnect the module from the account, + // re-linking it will allow any previous keys + function disable() external { + revert("Cannot disable module without removing it from account"); + } + + function _addValidationKey(bytes memory key) internal returns (bool) { (bytes32[2] memory key32, string memory originDomain) = abi.decode(key, (bytes32[2], string)); bytes32 initialLowerHalf = lowerKeyHalf[originDomain][msg.sender]; bytes32 initialUpperHalf = upperKeyHalf[originDomain][msg.sender]; @@ -34,7 +53,15 @@ contract WebAuthValidator is VerifierCaller, IModuleValidator { return initialLowerHalf == 0 && initialUpperHalf == 0; } - function handleValidation(bytes32 signedHash, bytes memory signature) external view returns (bool) { + function validateSignature(bytes32 signedHash, bytes memory signature) external view returns (bool) { + return webAuthVerify(signedHash, signature); + } + + function validateTransaction( + bytes32 signedHash, + bytes memory signature, + Transaction calldata _transaction + ) external view returns (bool) { return webAuthVerify(signedHash, signature); } diff --git a/test/BasicTest.ts b/test/BasicTest.ts index 39dd125b..b5f25ff2 100644 --- a/test/BasicTest.ts +++ b/test/BasicTest.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { parseEther, randomBytes } from "ethers"; +import { ethers, parseEther, randomBytes } from "ethers"; import { Wallet, ZeroAddress } from "ethers"; import { it } from "mocha"; import { SmartAccount, utils } from "zksync-ethers"; @@ -36,17 +36,40 @@ describe("Basic tests", function () { const aaFactoryContract = await fixtures.getAaFactory(); assert(aaFactoryContract != null, "No AA Factory deployed"); + const factoryAddress = await aaFactoryContract.getAddress(); + expect(factoryAddress, "the factory address").to.equal(await fixtures.getAaFactoryAddress(), "factory address match"); + + const bytecodeHash = await aaFactoryContract.beaconProxyBytecodeHash(); + const deployedAccountContract = await fixtures.getAccountProxyContract(); + const deployedAccountContractCode = await deployedAccountContract.getDeployedCode(); + assert(deployedAccountContractCode != null, "No account code deployed"); + const ssoBeaconBytecodeHash = ethers.hexlify(utils.hashBytecode(deployedAccountContractCode)); + expect(bytecodeHash, "deployed account bytecode hash").to.equal(ssoBeaconBytecodeHash, "deployed account code doesn't match"); + + const args = await aaFactoryContract.getEncodedBeacon(); + const deployedBeaconAddress = new ethers.AbiCoder().encode(["address"], [await fixtures.getBeaconAddress()]); + expect(args, "the beacon address").to.equal(deployedBeaconAddress, "the deployment beacon"); + + const randomSalt = randomBytes(32); + const standardCreate2Address = utils.create2Address(factoryAddress, bytecodeHash, randomSalt, args) ; + + const preDeployAccountCode = await fixtures.wallet.provider.getCode(standardCreate2Address); + expect(preDeployAccountCode , "expected deploy location").to.equal("0x", "nothing deployed here (yet)"); + const deployTx = await aaFactoryContract.deployProxySsoAccount( - randomBytes(32), - "id", - [], + randomSalt, + "id" + randomBytes(32).toString(), [], [fixtures.wallet.address], ); const deployTxReceipt = await deployTx.wait(); proxyAccountAddress = deployTxReceipt!.contractAddress!; + const postDeployAccountCode = await fixtures.wallet.provider.getCode(standardCreate2Address); + expect(postDeployAccountCode, "expected deploy location").to.not.equal("0x", "deployment didn't match create2!"); + expect(proxyAccountAddress, "the proxy account location via logs").to.not.equal(ZeroAddress, "be a valid address"); + expect(proxyAccountAddress, "the proxy account location").to.equal(standardCreate2Address, "be what create2 returns"); const account = SsoAccount__factory.connect(proxyAccountAddress, provider); assert(await account.k1IsOwner(fixtures.wallet.address)); @@ -72,8 +95,7 @@ describe("Basic tests", function () { value, gasLimit: 300_000n, }; - // TODO: fix gas estimation - // aaTx.gasLimit = await provider.estimateGas(aaTx); + aaTx.gasLimit = await provider.estimateGas(aaTx); const signedTransaction = await smartAccount.signTransaction(aaTx); assert(signedTransaction != null, "valid transaction to sign"); diff --git a/test/SessionKeyTest.ts b/test/SessionKeyTest.ts index 9ae27db9..281d0f50 100644 --- a/test/SessionKeyTest.ts +++ b/test/SessionKeyTest.ts @@ -6,8 +6,8 @@ import { it } from "mocha"; import { SmartAccount, utils } from "zksync-ethers"; import type { ERC20 } from "../typechain-types"; -import { AAFactory__factory, SsoAccount__factory } from "../typechain-types"; -import type { AAFactory } from "../typechain-types/src/AAFactory"; +import { SsoBeacon__factory, SsoAccount__factory } from "../typechain-types"; +import type { SsoBeacon } from "../typechain-types/src/SsoBeacon"; import type { SessionLib } from "../typechain-types/src/validators/SessionKeyValidator"; import { ContractFixtures, getProvider, logInfo } from "./utils"; @@ -148,14 +148,14 @@ class SessionTester { this.sessionOwner = new Wallet(Wallet.createRandom().privateKey, provider); this.sessionAccount = new SmartAccount({ payloadSigner: async (hash) => abiCoder.encode( - ["bytes", "address", "bytes[]"], + ["bytes", "address", "bytes"], [ this.sessionOwner.signingKey.sign(hash).serialized, sessionKeyModuleAddress, - [abiCoder.encode( + abiCoder.encode( [sessionSpecAbi, "uint64[]"], [this.session, await this.periodIds(this.aaTransaction.to!, this.aaTransaction.data?.slice(0, 10))], - )], // this array supplies data for hooks + ), ], ), address: this.proxyAccountAddress, @@ -178,7 +178,7 @@ class SessionTester { const aaTx = { ...await this.aaTxTemplate(), to: await sessionKeyModuleContract.getAddress(), - data: sessionKeyModuleContract.interface.encodeFunctionData("createSession", [this.session]), + data: sessionKeyModuleContract.interface.encodeFunctionData("createSession", [this.session]) }; aaTx.gasLimit = await provider.estimateGas(aaTx); @@ -250,12 +250,11 @@ class SessionTester { async sendTxSuccess(txRequest: ethers.TransactionLike = {}) { this.aaTransaction = { - ...await this.aaTxTemplate(), + ...await this.aaTxTemplate(await this.periodIds(txRequest.to!, txRequest.data?.slice(0, 10))), ...txRequest, }; - const estimatedGas = await provider.estimateGas(this.aaTransaction); - this.aaTransaction.gasLimit = BigInt(Math.ceil(Number(estimatedGas) * 1.1)); - logInfo(`\`sessionTx\` gas estimated: ${await provider.estimateGas(this.aaTransaction)}`); + this.aaTransaction.gasLimit = await provider.estimateGas(this.aaTransaction); + logInfo(`\`sessionTx\` gas estimated: ${this.aaTransaction.gasLimit}`); const signedTransaction = await this.sessionAccount.signTransaction(this.aaTransaction); const tx = await provider.broadcastTransaction(signedTransaction); @@ -265,7 +264,7 @@ class SessionTester { async sendTxFail(tx: ethers.TransactionLike = {}) { this.aaTransaction = { - ...await this.aaTxTemplate(), + ...await this.aaTxTemplate(await this.periodIds(tx.to!, tx.data?.slice(0, 10))), gasLimit: 100_000_000n, ...tx, }; @@ -277,21 +276,21 @@ class SessionTester { getLimit(limit?: PartialLimit): SessionLib.UsageLimitStruct { return limit == null ? { - limitType: LimitType.Unlimited, - limit: 0, - period: 0, - } + limitType: LimitType.Unlimited, + limit: 0, + period: 0, + } : limit.period == null ? { - limitType: LimitType.Lifetime, - limit: limit.limit, - period: 0, - } + limitType: LimitType.Lifetime, + limit: limit.limit, + period: 0, + } : { - limitType: LimitType.Allowance, - limit: limit.limit, - period: limit.period, - }; + limitType: LimitType.Allowance, + limit: limit.limit, + period: limit.period, + }; } getSession(session: PartialSession): SessionLib.SessionSpecStruct { @@ -320,8 +319,7 @@ class SessionTester { }; } - async aaTxTemplate() { - const numberOfConstraints = this.session.callPolicies.map((policy) => policy.constraints.length); + async aaTxTemplate(periodIds?: number[]) { return { type: 113, from: this.proxyAccountAddress, @@ -332,17 +330,17 @@ class SessionTester { gasPrice: await provider.getGasPrice(), customData: { gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, - customSignature: abiCoder.encode( - ["bytes", "address", "bytes[]"], + customSignature: periodIds ? abiCoder.encode( + ["bytes", "address", "bytes"], [ ethers.zeroPadValue("0x1b", 65), await fixtures.getSessionKeyModuleAddress(), - [abiCoder.encode( + abiCoder.encode( [sessionSpecAbi, "uint64[]"], - [this.session, new Array(2 + Math.max(0, ...numberOfConstraints)).fill(0)], - )], + [this.session, periodIds], + ), ], - ), + ) : undefined, }, gasLimit: 0n, }; @@ -367,6 +365,8 @@ describe("SessionKeyModule tests", function () { assert(sessionModuleContract != null, "No session module deployed"); const ssoContract = await fixtures.getAccountImplContract(); assert(ssoContract != null, "No SSO Account deployed"); + const beaconContract = await fixtures.getBeaconContract(); + assert(beaconContract != null, "No Beacon deployed"); const factoryContract = await fixtures.getAaFactory(); assert(factoryContract != null, "No AA Factory deployed"); const authServerPaymaster = await fixtures.deployExampleAuthServerPaymaster( @@ -375,6 +375,8 @@ describe("SessionKeyModule tests", function () { ); assert(authServerPaymaster != null, "No Auth Server Paymaster deployed"); + logInfo(`SSO Account Address : ${await ssoContract.getAddress()}`); + logInfo(`Beacon Address : ${await beaconContract.getAddress()}`); logInfo(`Session Address : ${await sessionModuleContract.getAddress()}`); logInfo(`Passkey Address : ${await verifierContract.getAddress()}`); logInfo(`Account Factory Address : ${await factoryContract.getAddress()}`); @@ -388,8 +390,7 @@ describe("SessionKeyModule tests", function () { const deployTx = await factoryContract.deployProxySsoAccount( randomBytes(32), - "id", - [], + "session-key-test-id", [sessionKeyPayload], [fixtures.wallet.address], ); @@ -403,7 +404,7 @@ describe("SessionKeyModule tests", function () { const account = SsoAccount__factory.connect(proxyAccountAddress, provider); assert(await account.k1IsOwner(fixtures.wallet.address)); - assert(await account.isHook(sessionKeyModuleAddress), "session key module should be a hook"); + assert(!await account.isHook(sessionKeyModuleAddress), "session key module should not be an execution hook"); assert(await account.isModuleValidator(sessionKeyModuleAddress), "session key module should be a validator"); }); @@ -436,6 +437,7 @@ describe("SessionKeyModule tests", function () { value: parseEther("0.02"), }); }); + }); describe("ERC20 transfer limit tests", function () { @@ -581,10 +583,10 @@ describe("SessionKeyModule tests", function () { }); describe("Upgrade tests", function () { - let beacon: AAFactory; + let beacon: SsoBeacon; it("should check implementation address", async () => { - beacon = AAFactory__factory.connect(await fixtures.getAaFactoryAddress(), fixtures.wallet); + beacon = SsoBeacon__factory.connect(await fixtures.getBeaconAddress(), fixtures.wallet); expect(await beacon.implementation()).to.equal(await fixtures.getAccountImplAddress()); }); @@ -598,10 +600,13 @@ describe("SessionKeyModule tests", function () { const proxy = new ethers.Contract(proxyAccountAddress, ["function dummy() pure returns (string)"], provider); expect(await proxy.dummy()).to.equal("dummy"); }); + + it("should upgrade implementation back", async () => { + await beacon.upgradeTo(await fixtures.getAccountImplAddress()); + expect(await beacon.implementation()).to.equal(await fixtures.getAccountImplAddress()); + }); }); // TODO: module uninstall tests - // TODO: session expiresAt tests // TODO: session fee limit tests - // TODO: allowance tests }); diff --git a/test/utils.ts b/test/utils.ts index d4fa7ca4..aca4b70b 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -2,31 +2,32 @@ import "@matterlabs/hardhat-zksync-node/dist/type-extensions"; import "@matterlabs/hardhat-zksync-verify/dist/src/type-extensions"; import dotenv from "dotenv"; -import { ethers, parseEther } from "ethers"; +import { ethers, parseEther, randomBytes } from "ethers"; import { readFileSync } from "fs"; import { promises } from "fs"; import * as hre from "hardhat"; import { ContractFactory, Provider, utils, Wallet } from "zksync-ethers"; import { base64UrlToUint8Array, getPublicKeyBytesFromPasskeySignature, unwrapEC2Signature } from "zksync-sso/utils"; -import { AAFactory, ERC20, ExampleAuthServerPaymaster, SessionKeyValidator, SsoAccount, WebAuthValidator } from "../typechain-types"; -import { AAFactory__factory, ERC20__factory, ExampleAuthServerPaymaster__factory, SessionKeyValidator__factory, SsoAccount__factory, WebAuthValidator__factory } from "../typechain-types"; +import { AAFactory, ERC20, ExampleAuthServerPaymaster, SessionKeyValidator, SsoAccount, WebAuthValidator, SsoBeacon, AccountProxy__factory, AccountProxy } from "../typechain-types"; +import { AAFactory__factory, ERC20__factory, ExampleAuthServerPaymaster__factory, SessionKeyValidator__factory, SsoAccount__factory, WebAuthValidator__factory, SsoBeacon__factory } from "../typechain-types"; + +export const ethersStaticSalt = new Uint8Array([ + 205, 241, 161, 186, 101, 105, 79, + 248, 98, 64, 50, 124, 168, 204, + 200, 71, 214, 169, 195, 118, 199, + 62, 140, 111, 128, 47, 32, 21, + 177, 177, 174, 166, +]); export class ContractFixtures { readonly wallet: Wallet = getWallet(LOCAL_RICH_WALLETS[0].privateKey); - readonly ethersStaticSalt = new Uint8Array([ - 205, 241, 161, 186, 101, 105, 79, - 248, 98, 64, 50, 124, 168, 204, - 200, 71, 214, 169, 195, 118, 199, - 62, 140, 111, 128, 47, 32, 21, - 177, 177, 174, 166, - ]); private _aaFactory: AAFactory; async getAaFactory() { - const implAddress = await this.getAccountImplAddress(); + const beaconAddress = await this.getBeaconAddress(); if (!this._aaFactory) { - this._aaFactory = await deployFactory(this.wallet, implAddress); + this._aaFactory = await deployFactory(this.wallet, beaconAddress, ethersStaticSalt); } return this._aaFactory; } @@ -38,7 +39,7 @@ export class ContractFixtures { private _sessionKeyModule: SessionKeyValidator; async getSessionKeyContract() { if (!this._sessionKeyModule) { - const contract = await create2("SessionKeyValidator", this.wallet, this.ethersStaticSalt); + const contract = await create2("SessionKeyValidator", this.wallet, ethersStaticSalt); this._sessionKeyModule = SessionKeyValidator__factory.connect(await contract.getAddress(), this.wallet); } return this._sessionKeyModule; @@ -48,46 +49,58 @@ export class ContractFixtures { return (await this.getSessionKeyContract()).getAddress(); } + private _beacon: SsoBeacon; + async getBeaconContract() { + if (!this._beacon) { + const implAddress = await this.getAccountImplAddress(); + const contract = await create2("SsoBeacon", this.wallet, ethersStaticSalt, [implAddress]); + this._beacon = SsoBeacon__factory.connect(await contract.getAddress(), this.wallet); + } + return this._beacon; + } + + async getBeaconAddress() { + return (await this.getBeaconContract()).getAddress(); + } + private _webauthnValidatorModule: WebAuthValidator; // does passkey validation via modular interface async getWebAuthnVerifierContract() { if (!this._webauthnValidatorModule) { - const contract = await create2("WebAuthValidator", this.wallet, this.ethersStaticSalt); + const contract = await create2("WebAuthValidator", this.wallet, ethersStaticSalt); this._webauthnValidatorModule = WebAuthValidator__factory.connect(await contract.getAddress(), this.wallet); } return this._webauthnValidatorModule; } - private _passkeyModuleAddress: string; async getPasskeyModuleAddress() { - if (!this._passkeyModuleAddress) { - const passkeyModule = await this.getWebAuthnVerifierContract(); - this._passkeyModuleAddress = await passkeyModule.getAddress(); - } - return this._passkeyModuleAddress; + return (await this.getWebAuthnVerifierContract()).getAddress(); } private _accountImplContract: SsoAccount; - async getAccountImplContract() { + async getAccountImplContract(salt?: ethers.BytesLike) { if (!this._accountImplContract) { - const contract = await create2("SsoAccount", this.wallet, this.ethersStaticSalt); + const contract = await create2("SsoAccount", this.wallet, salt ?? ethersStaticSalt); this._accountImplContract = SsoAccount__factory.connect(await contract.getAddress(), this.wallet); } return this._accountImplContract; } - private _accountImplAddress: string; - // deploys the base account for future proxy use - async getAccountImplAddress() { - if (!this._accountImplAddress) { - const accountImpl = await this.getAccountImplContract(); - this._accountImplAddress = await accountImpl.getAddress(); + private _accountProxyContract: AccountProxy; + async getAccountProxyContract() { + if (!this._accountProxyContract) { + const contract = await create2("AccountProxy", this.wallet, ethersStaticSalt, [await this.getBeaconAddress()]); + this._accountProxyContract = AccountProxy__factory.connect(await contract.getAddress(), this.wallet); } - return this._accountImplAddress; + return this._accountProxyContract; + } + + async getAccountImplAddress(salt?: ethers.BytesLike) { + return (await this.getAccountImplContract(salt)).getAddress(); } async deployERC20(mintTo: string): Promise { - const contract = await create2("TestERC20", this.wallet, this.ethersStaticSalt, [mintTo]); + const contract = await create2("TestERC20", this.wallet, ethersStaticSalt, [mintTo]); return ERC20__factory.connect(await contract.getAddress(), this.wallet); } @@ -98,7 +111,7 @@ export class ContractFixtures { const contract = await create2( "ExampleAuthServerPaymaster", this.wallet, - this.ethersStaticSalt, + ethersStaticSalt, [ aaFactoryAddress, sessionKeyValidatorAddress, @@ -141,16 +154,26 @@ export const getProviderL1 = () => { return provider; }; -export async function deployFactory(wallet: Wallet, implAddress: string): Promise { +export async function deployFactory(wallet: Wallet, beaconAddress: string, salt?: ethers.BytesLike): Promise { const factoryArtifact = JSON.parse(await promises.readFile("artifacts-zk/src/AAFactory.sol/AAFactory.json", "utf8")); const proxyAaArtifact = JSON.parse(await promises.readFile("artifacts-zk/src/AccountProxy.sol/AccountProxy.json", "utf8")); - const deployer = new ContractFactory(factoryArtifact.abi, factoryArtifact.bytecode, wallet); + const deployer = new ContractFactory(factoryArtifact.abi, factoryArtifact.bytecode, wallet, "create2"); const bytecodeHash = utils.hashBytecode(proxyAaArtifact.bytecode); + const factoryBytecodeHash = utils.hashBytecode(factoryArtifact.bytecode); + const factorySalt = ethers.hexlify(salt ?? randomBytes(32)); + const constructorArgs = deployer.interface.encodeDeploy([bytecodeHash, beaconAddress]); + const standardCreate2Address = utils.create2Address(wallet.address, factoryBytecodeHash, factorySalt, constructorArgs); + const accountCode = await wallet.provider.getCode(standardCreate2Address); + if (accountCode != "0x") { + logInfo(`Factory already exists!`); + return AAFactory__factory.connect(standardCreate2Address, wallet); + } + logInfo(`Factory doesn't exist at ${standardCreate2Address}!`); const factory = await deployer.deploy( bytecodeHash, - implAddress, - { customData: { factoryDeps: [proxyAaArtifact.bytecode] } }, + beaconAddress, + { customData: { salt: factorySalt, factoryDeps: [proxyAaArtifact.bytecode] } }, ); const factoryAddress = await factory.getAddress(); @@ -160,7 +183,7 @@ export async function deployFactory(wallet: Wallet, implAddress: string): Promis await verifyContract({ address: factoryAddress, contract: `src/AAFactory.sol:AAFactory`, - constructorArguments: deployer.interface.encodeDeploy([bytecodeHash, implAddress]), + constructorArguments: deployer.interface.encodeDeploy([bytecodeHash, beaconAddress]), bytecode: factoryArtifact.bytecode, }); } @@ -171,7 +194,7 @@ export async function deployFactory(wallet: Wallet, implAddress: string): Promis export const getWallet = (privateKey?: string) => { if (!privateKey) { // Get wallet private key from .env file - if (!process.env.WALLET_PRIVATE_KEY) throw "⛔️ Wallet private key wasn't found in .env file!"; + if (!process.env.WALLET_PRIVATE_KEY) throw "Wallet private key wasn't found in .env file!"; } const provider = getProvider(); @@ -186,7 +209,7 @@ export const getWallet = (privateKey?: string) => { export const verifyEnoughBalance = async (wallet: Wallet, amount: bigint) => { // Check if the wallet has enough balance const balance = await wallet.getBalance(); - if (balance < amount) throw `⛔️ Wallet balance is too low! Required ${ethers.formatEther(amount)} ETH, but current ${wallet.address} balance is ${ethers.formatEther(balance)} ETH`; + if (balance < amount) throw `Wallet balance is too low! Required ${ethers.formatEther(amount)} ETH, but current ${wallet.address} balance is ${ethers.formatEther(balance)} ETH`; }; /** @@ -215,16 +238,6 @@ export const create2 = async (contractName: string, wallet: Wallet, salt: ethers const accountCode = await wallet.provider.getCode(standardCreate2Address); if (accountCode != "0x") { logInfo(`Contract ${contractName} already exists!`); - // if (hre.network.config.verifyURL) { - // logInfo(`Requesting contract verification...`); - // await verifyContract({ - // address: standardCreate2Address, - // contract: `${contractArtifact.sourceName}:${contractName}`, - // constructorArguments: constructorArgs, - // bytecode: accountCode, - // }); - // } - return new ethers.Contract(standardCreate2Address, contractArtifact.abi, wallet); } @@ -270,13 +283,13 @@ const masterWallet = ethers.Wallet.fromPhrase("stuff slice staff easily soup par export const LOCAL_RICH_WALLETS = [ hre.network.name == "dockerizedNode" ? { - address: masterWallet.address, - privateKey: masterWallet.privateKey, - } + address: masterWallet.address, + privateKey: masterWallet.privateKey, + } : { - address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - }, + address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + }, { address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110",