Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,91 @@ jobs:

- name: Run integration tests
run: pnpm test

zksync-os:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v5

- name: Install Foundry
uses: foundry-rs/[email protected]
with:
# zksync-os-server's zkos-l1-state.json doesn't work on latest version of anvil
version: v1.3.4

- name: Install dependencies
run: forge soldeer install

- name: Build contracts
run: forge build

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.20.0

- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: lts/Jod
cache: pnpm

- name: Install dependencies
run: |
pnpm install -r --frozen-lockfile
cd ${{ github.workspace }}/..
git clone https://github.com/eth-infinitism/account-abstraction
git clone https://github.com/matter-labs/zksync-os-server

- name: Cache Rust build
uses: Swatinem/rust-cache@v2
with:
workspaces: "../zksync-os-server -> target"

- name: Run server
run: |
cd ${{ github.workspace }}/../zksync-os-server
cargo build --release --bin zksync-os-server
anvil --load-state zkos-l1-state.json --port 8545 &> anvil.log &
cargo run --release --bin zksync-os-server &> server.log &

- name: Deploy entrypoint
run: |
cd ${{ github.workspace }}/../account-abstraction
git checkout v0.8.0
sed -i "60a zksyncos: { url: 'http://localhost:3050', accounts: ['0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'] }," hardhat.config.ts
npm install -g yarn
yarn install
yarn deploy --network zksyncos

- name: Fund wallet
run: |
# Rich account
PRIVATE_KEY=0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110
# send 10 ETH
TO=0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
cast send --private-key ${PRIVATE_KEY} --rpc-url http://localhost:3050 ${TO} --value 10000000000000000000

- name: Deploy contracts and a test account
run: pnpm deploy-test:zksync-os

- name: Run bundler
run: |
pnpm bundler:zksync-os &> bundler.log &
sleep 5

- name: Run integration tests
run: pnpm test:zksync-os

- name: Print anvil logs
if: always()
run: cat ${{ github.workspace }}/../zksync-os-server/anvil.log

- name: Print server logs
if: always()
run: cat ${{ github.workspace }}/../zksync-os-server/server.log

- name: Print bundler logs
if: always()
run: cat bundler.log
8 changes: 8 additions & 0 deletions alto-zksync-os.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"entrypoints": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
"executor-private-keys": "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110",
"utility-private-key": "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110",
"rpc-url": "http://localhost:3050",
"port": 4337,
"safe-mode": false
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
"scripts": {
"bundler": "alto --config ./alto.json",
"bundler:with-proxy": "node js/bundler-with-proxy.js",
"bundler:zksync-os": "alto --config ./alto-zksync-os.json",
"bundler-proxy": "node js/bundler-proxy.js",
"test": "node --test --import tsx --test-concurrency=1 test/integration/*.test.ts",
"test:zksync-os": "CHAIN_ID=6565 PORT=3050 node --test --import tsx --test-concurrency=1 test/integration/*.test.ts",
"anvil": "anvil --fork-url https://reth-ethereum.ithaca.xyz/rpc --chain-id 1337",
"coverage": "forge coverage --ir-minimum --no-match-coverage 'script|test'",
"//": "Uses the last private key of anvil's rich accounts list",
"deploy-test": "forge script script/Deploy.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --sig 'deployAll()'"
"deploy-test": "forge script script/Deploy.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --sig 'deployAll()'",
"deploy-test:zksync-os": "forge script script/Deploy.s.sol --rpc-url http://127.0.0.1:3050 --broadcast --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --sig 'deployAll()'"
},
"keywords": [
"zksync",
Expand Down
5 changes: 3 additions & 2 deletions test/integration/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "viem/account-abstraction";

import { hashTypedData, wrapTypedDataSignature } from "viem/experimental/erc7739";
import { chainId } from "./utils";

const callAbi = [{
components: [
Expand Down Expand Up @@ -78,7 +79,7 @@ export class SsoAccount {
userOperation: { ...userOperation, sender: address },
entryPointAddress: entryPoint08Address,
entryPointVersion: '0.8',
chainId: 1337
chainId: chainId(),
});
return await sso.signer(userOpHash);
},
Expand All @@ -96,7 +97,7 @@ export class SsoAccount {
},
async signTypedData(typedData) {
const verifierDomain = {
chainId: 1337,
chainId: chainId(),
name: "zksync-sso-1271",
version: "1.0.0",
verifyingContract: address,
Expand Down
19 changes: 9 additions & 10 deletions test/integration/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import { parseAbi } from "viem";
import { privateKeyToAccount } from "viem/accounts";

import { SsoAccount } from "./account";
import { contractAddresses, toEOASigner, createClients, randomAddress, deployContract } from "./utils";
import { contractAddresses, toEOASigner, createClients, randomAddress, deployContract, chainId, rpcPort } from "./utils";

const anvilPort = 8545;
const altoPort = require("../../alto.json").port;
const privateKey = "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6";

test("executes a simple transfer signed using EOA", { timeout: 120_000 }, async () => {
const { account } = contractAddresses();
const { client, bundlerClient } = createClients(anvilPort, altoPort);
const { client, bundlerClient } = createClients(rpcPort(), altoPort);
const sso = await SsoAccount.create(client, account, toEOASigner(privateKey));

const target = randomAddress();
Expand All @@ -24,7 +23,7 @@ test("executes a simple transfer signed using EOA", { timeout: 120_000 }, async
}],
});

const receipt = await bundlerClient.waitForUserOperationReceipt({ hash, timeout: 0 });
const receipt = await bundlerClient.waitForUserOperationReceipt({ hash, timeout: 2_000 });
assert.equal(
receipt.receipt.status,
"success",
Expand All @@ -37,7 +36,7 @@ test("executes a simple transfer signed using EOA", { timeout: 120_000 }, async

test("executes a transaction sponsored by a paymaster", { timeout: 120_000 }, async () => {
const { account } = contractAddresses();
const { client, bundlerClient } = createClients(anvilPort, altoPort);
const { client, bundlerClient } = createClients(rpcPort(), altoPort);
const sso = await SsoAccount.create(client, account, toEOASigner(privateKey));
const deployer = privateKeyToAccount(privateKey);
const paymaster = await deployContract(client, privateKey, "MockPaymaster");
Expand All @@ -47,9 +46,9 @@ test("executes a transaction sponsored by a paymaster", { timeout: 120_000 }, as
abi: parseAbi(["function deposit() external payable"]),
functionName: "deposit",
args: [],
value: 10n ** 18n, // 1 ETH
value: 10n ** 17n, // 0.1 ETH
})
await client.waitForTransactionReceipt({ hash: depositHash, timeout: 0 });
await client.waitForTransactionReceipt({ hash: depositHash, timeout: 2_000 });

const balanceBefore = await client.getBalance({ address: account });
const sponsored = await bundlerClient.sendUserOperation({
Expand All @@ -58,7 +57,7 @@ test("executes a transaction sponsored by a paymaster", { timeout: 120_000 }, as
paymaster,
});

const receipt = await bundlerClient.waitForUserOperationReceipt({ hash: sponsored, timeout: 0 });
const receipt = await bundlerClient.waitForUserOperationReceipt({ hash: sponsored, timeout: 2_000 });
const balanceAfter = await client.getBalance({ address: account });

assert.equal(receipt.receipt.status, "success", "sponsored user operation should execute successfully");
Expand All @@ -67,7 +66,7 @@ test("executes a transaction sponsored by a paymaster", { timeout: 120_000 }, as

test("checks ERC7739 EOA signature using ERC1271", { timeout: 120_000 }, async () => {
const { account } = contractAddresses();
const { client } = createClients(anvilPort, altoPort);
const { client } = createClients(rpcPort(), altoPort);
const sso = await SsoAccount.create(client, account, toEOASigner(privateKey));

const erc1271Caller = await deployContract(client, privateKey, "MockERC1271Caller");
Expand All @@ -84,7 +83,7 @@ test("checks ERC7739 EOA signature using ERC1271", { timeout: 120_000 }, async (
]
},
domain: {
chainId: 1337,
chainId: chainId(),
name: "ERC1271Caller",
version: "1.0.0",
verifyingContract: erc1271Caller,
Expand Down
15 changes: 7 additions & 8 deletions test/integration/passkey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import crypto from "crypto";
import { encodeFunctionData, toHex, parseAbi } from "viem";

import { SsoAccount } from "./account";
import { contractAddresses, toEOASigner, toPasskeySigner, createClients, randomAddress, deployContract } from "./utils";
import { contractAddresses, toEOASigner, toPasskeySigner, createClients, randomAddress, deployContract, chainId, rpcPort } from "./utils";

const anvilPort = 8545;
const altoPort = require("../../alto.json").port;
const privateKey = "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6";

Expand All @@ -20,7 +19,7 @@ const publicKey = {

test("adds a Passkey to the account", { timeout: 120_000 }, async () => {
const { account, webauthnValidator } = contractAddresses();
const { client, bundlerClient } = createClients(anvilPort, altoPort);
const { client, bundlerClient } = createClients(rpcPort(), altoPort);
const sso = await SsoAccount.create(client, account, toEOASigner(privateKey));

// add validation key via the passkey validator contract
Expand All @@ -36,7 +35,7 @@ test("adds a Passkey to the account", { timeout: 120_000 }, async () => {
}],
});

const receipt = await bundlerClient.waitForUserOperationReceipt({ hash, timeout: 0 });
const receipt = await bundlerClient.waitForUserOperationReceipt({ hash, timeout: 2_000 });
assert.equal(
receipt.receipt.status,
"success",
Expand All @@ -46,7 +45,7 @@ test("adds a Passkey to the account", { timeout: 120_000 }, async () => {

test("executes a simple transfer signed using Passkey", { timeout: 120_000 }, async () => {
const { account } = contractAddresses();
const { client, bundlerClient } = createClients(anvilPort, altoPort);
const { client, bundlerClient } = createClients(rpcPort(), altoPort);
const sso = await SsoAccount.create(client, account, toPasskeySigner(keyPair.privateKey, credentialId));

// transfer to a random address using passkey signer
Expand All @@ -59,7 +58,7 @@ test("executes a simple transfer signed using Passkey", { timeout: 120_000 }, as
}],
});

const receipt = await bundlerClient.waitForUserOperationReceipt({ hash, timeout: 0 });
const receipt = await bundlerClient.waitForUserOperationReceipt({ hash, timeout: 2_000 });
assert.equal(
receipt.receipt.status,
"success",
Expand All @@ -72,7 +71,7 @@ test("executes a simple transfer signed using Passkey", { timeout: 120_000 }, as

test("checks ERC7739 Passkey signature using ERC1271", { timeout: 120_000 }, async () => {
const { account } = contractAddresses();
const { client } = createClients(anvilPort, altoPort);
const { client } = createClients(rpcPort(), altoPort);
const sso = await SsoAccount.create(client, account, toPasskeySigner(keyPair.privateKey, credentialId));

const erc1271Caller = await deployContract(client, privateKey, "MockERC1271Caller");
Expand All @@ -89,7 +88,7 @@ test("checks ERC7739 Passkey signature using ERC1271", { timeout: 120_000 }, asy
]
},
domain: {
chainId: 1337,
chainId: chainId(),
name: "ERC1271Caller",
version: "1.0.0",
verifyingContract: erc1271Caller,
Expand Down
17 changes: 13 additions & 4 deletions test/integration/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@ import {
concat,
type Hex,
type Address,
type WalletClient,
type PublicClient,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { createBundlerClient } from "viem/account-abstraction";
import { localhost } from "viem/chains";

import crypto from "crypto";

export function chainId() {
return Number(process.env.CHAIN_ID || '1337');
}

export function rpcPort() {
return Number(process.env.PORT || '8545');
}

export function contractAddresses() {
const txs = require('../../broadcast/Deploy.s.sol/1337/deployAll-latest.json').transactions;
const txs = require(`../../broadcast/Deploy.s.sol/${chainId()}/deployAll-latest.json`).transactions;
return {
eoaValidator: txs[1].contractAddress as Address,
sessionValidator: txs[3].contractAddress as Address,
Expand All @@ -33,6 +39,9 @@ export function createClients(anvilPort: number, bundlerPort: number) {
// smaller polling interval to speed up the test
const pollingInterval = 100;

// @ts-ignore
localhost.id = chainId();

const client = createPublicClient({
chain: localhost,
transport: http(`http://localhost:${anvilPort}`),
Expand All @@ -59,7 +68,7 @@ export async function deployContract(client: any, privateKey: Hex, name: string)
abi: [],
bytecode: require(`../../out/${name}.sol/${name}.json`).bytecode.object
});
const deployment = await client.waitForTransactionReceipt({ hash: deploymentHash, timeout: 100 });
const deployment = await client.waitForTransactionReceipt({ hash: deploymentHash, timeout: 2_000 });
return deployment.contractAddress!;
}

Expand Down