Skip to content

Commit

Permalink
Merge branch 'main' into update-reamde
Browse files Browse the repository at this point in the history
  • Loading branch information
MexicanAce authored Dec 14, 2024
2 parents fc57f83 + 28ce388 commit e3759e4
Show file tree
Hide file tree
Showing 37 changed files with 486 additions and 1,002 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ node_modules/

# era-test-node
era_test_node.log
anvil-zksync.log

package-lock.json
yarn.lock
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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": "[email protected]"
}
162 changes: 81 additions & 81 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

72 changes: 34 additions & 38 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
// 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);
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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 <value>) address must be provided to deploy factory";
throw "Account implementation (--implementation <value>) address must be provided to deploy beacon";
}
args = [cmd.implementation];
}
if (cmd.only == FACTORY_NAME) {
if (!cmd.implementation) {
throw "Beacon (--beacon <value>) address must be provided to deploy factory";
}
args = [cmd.implementation];
}
if (cmd.only == PAYMASTER_NAME) {
if (!cmd.factory || !cmd.sessions) {
throw "⛔️ Factory (--factory <value>) and SessionModule (--sessions <value>) addresses must be provided to deploy paymaster";
throw "Factory (--factory <value>) and SessionModule (--sessions <value>) 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);
}
}
});
41 changes: 41 additions & 0 deletions scripts/upgrade.ts
Original file line number Diff line number Diff line change
@@ -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 <value>'";
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");
});
28 changes: 13 additions & 15 deletions src/AAFactory.sol
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -11,38 +10,42 @@ import { ISsoAccount } from "./interfaces/ISsoAccount.sol";
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @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.
/// @dev Uses `create2` to deploy a proxy account, allowing for deterministic addresses based on the provided salt.
/// @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");
Expand All @@ -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;

Expand Down
Loading

0 comments on commit e3759e4

Please sign in to comment.