Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] feat: add guardian recovery #261

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e9c2b5e
feat: empty GuardianRecoveryValidator
calvogenerico Jan 9, 2025
df852cf
feat: methods to add a guardian
calvogenerico Jan 9, 2025
3972ec2
fix: reverting when guardian not found
calvogenerico Jan 9, 2025
1a6def0
fix: uint to uint256
calvogenerico Jan 9, 2025
b1478cd
feat: add validateTransaction implementation to GuardianRecoveryValid…
MiniRoman Jan 17, 2025
f01162c
chore: refactor tests
MiniRoman Jan 17, 2025
a7eec81
chore: clean up code
MiniRoman Jan 17, 2025
3290826
feat: improve init method
MiniRoman Jan 17, 2025
fde86e3
feat: simplify initRecovery method
MiniRoman Jan 22, 2025
731ffd7
chore: resolve build issues
MiniRoman Jan 22, 2025
d9dc82b
chore: resolve build issues
MiniRoman Jan 22, 2025
94ffc8b
chore: resolve pr comments
MiniRoman Jan 23, 2025
771c586
feat: restore guardiansFor method
MiniRoman Jan 23, 2025
b36bcb2
chore: remove unused access to accountGuardians
MiniRoman Jan 23, 2025
24f34e8
feat: make guardian recovery validator contract proxy-able
MiniRoman Jan 23, 2025
4c73093
chore: simplify initializer function name
MiniRoman Jan 24, 2025
4ae13ba
Merge pull request #1 from Moonsong-Labs/feat/guardian-module
aon Jan 24, 2025
064764b
feat: add function to retrieve guarded accounts
MiniRoman Jan 24, 2025
afb9c70
fix: improve recovery validator logic
aon Jan 24, 2025
a09d7e2
Merge pull request #2 from Moonsong-Labs/feat/guardian-module
aon Jan 24, 2025
460446c
feat: allow paymaster calls to GuardianRecoveryValidator
MiniRoman Jan 28, 2025
b408afc
Merge pull request #3 from Moonsong-Labs/feat/guardian-module
aon Jan 28, 2025
ddac18b
feat: merge from working branch
aon Jan 30, 2025
0ec4f89
feat: fix guardian recovery validator compilation
MiniRoman Jan 30, 2025
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@nomad-xyz/excessively-safe-call": "0.0.1-rc.1",
"@nomicfoundation/hardhat-chai-matchers": "2.0.8",
"@nomicfoundation/hardhat-ethers": "3.0.8",
"@nomicfoundation/hardhat-network-helpers": "^1.0.12",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@nomicfoundation/hardhat-verify": "2.0.11",
"@openzeppelin/contracts": "4.9.6",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { Wallet } from "zksync-ethers";

const WEBAUTH_NAME = "WebAuthValidator";
const SESSIONS_NAME = "SessionKeyValidator";
const GUARDIAN_RECOVERY_NAME = "GuardianRecoveryValidator";
const ACCOUNT_IMPL_NAME = "SsoAccount";
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> {
async function deploy(name: string, deployer: Wallet, proxy: boolean, args?: any[], initArgs?: any): Promise<string> {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { deployFactory, create2, ethersStaticSalt } = require("../test/utils");
console.log("Deploying", name, "contract...");
Expand All @@ -26,13 +27,12 @@ async function deploy(name: string, deployer: Wallet, proxy: boolean, args?: any
console.log(name, "contract deployed at:", implAddress, "\n");
return implAddress;
}
const proxyContract = await create2("TransparentProxy", deployer, ethersStaticSalt, [implAddress]);
const proxyContract = await create2("TransparentProxy", deployer, ethersStaticSalt, [implAddress, initArgs ?? "0x"]);
const proxyAddress = await proxyContract.getAddress();
console.log(name, "proxy contract deployed at:", proxyAddress, "\n");
return proxyAddress;
}


task("deploy", "Deploys ZKsync SSO contracts")
.addOptionalParam("only", "name of a specific contract to deploy")
.addFlag("noProxy", "do not deploy transparent proxies for factory and modules")
Expand Down Expand Up @@ -76,12 +76,14 @@ task("deploy", "Deploys ZKsync SSO contracts")
}

if (!cmd.only) {
await deploy(WEBAUTH_NAME, deployer, !cmd.noProxy);
const webauth = 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]);
const guardianInterface = new ethers.Interface((await hre.artifacts.readArtifact(GUARDIAN_RECOVERY_NAME)).abi);
const recovery = await deploy(GUARDIAN_RECOVERY_NAME, deployer, !cmd.noProxy, [webauth], guardianInterface.encodeFunctionData("initialize", [webauth]));
const paymaster = await deploy(PAYMASTER_NAME, deployer, false, [factory, sessions, recovery]);

await fundPaymaster(paymaster, cmd.fund);
} else {
Expand Down
5 changes: 4 additions & 1 deletion src/TransparentProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/trans
/// cheap delegate calls on ZKsync.
/// @dev This proxy is placed in front of `AAFactory` and all modules (`WebAuthValidator`, `SessionKeyValidator`).
contract TransparentProxy is TransparentUpgradeableProxy, EfficientProxy {
constructor(address implementation) TransparentUpgradeableProxy(implementation, msg.sender, bytes("")) {}
constructor(
address implementation,
bytes memory data
) TransparentUpgradeableProxy(implementation, msg.sender, data) {}

function _delegate(address implementation) internal override(EfficientProxy, Proxy) {
EfficientProxy._delegate(implementation);
Expand Down
28 changes: 28 additions & 0 deletions src/interfaces/IGuardianRecoveryValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
import { IModuleValidator } from "./IModuleValidator.sol";
import { Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol";

interface IGuardianRecoveryValidator is IModuleValidator {
struct GuardianConfirmation {
address ssoAccount;
}

function proposeValidationKey(address externalAccount) external;

function removeValidationKey(address externalAccount) external;

function initRecovery(address accountToRecover, bytes memory passkey) external;

// IModuleValidator
function addValidationKey(bytes memory key) external returns (bool);

// IModuleValidator
function validateTransaction(
bytes32 signedHash,
bytes memory signature,
Transaction calldata transaction
) external returns (bool);

// IModuleValidator
function validateSignature(bytes32 signedHash, bytes memory signature) external view returns (bool);
}
17 changes: 15 additions & 2 deletions src/test/ExampleAuthServerPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,31 @@ import "@openzeppelin/contracts/access/Ownable.sol";

import { AAFactory } from "../AAFactory.sol";
import { SessionKeyValidator } from "../validators/SessionKeyValidator.sol";
import { GuardianRecoveryValidator } from "../validators/GuardianRecoveryValidator.sol";

/// @author Matter Labs
/// @notice This contract does not include any validations other than using the paymaster general flow.
contract ExampleAuthServerPaymaster is IPaymaster, Ownable {
address public immutable AA_FACTORY_CONTRACT_ADDRESS;
address public immutable SESSION_KEY_VALIDATOR_CONTRACT_ADDRESS;
address public immutable ACCOUNT_RECOVERY_VALIDATOR_CONTRACT_ADDRESS;
bytes4 constant DEPLOY_ACCOUNT_SELECTOR = AAFactory.deployProxySsoAccount.selector;
bytes4 constant SESSION_CREATE_SELECTOR = SessionKeyValidator.createSession.selector;
bytes4 constant SESSION_REVOKE_KEY_SELECTOR = SessionKeyValidator.revokeKey.selector;
bytes4 constant SESSION_REVOKE_KEYS_SELECTOR = SessionKeyValidator.revokeKeys.selector;
bytes4 constant ACCOUNT_RECOVERY_ADD_KEY_SELECTOR = GuardianRecoveryValidator.addValidationKey.selector;
bytes4 constant ACCOUNT_RECOVERY_PROPOSE_KEY_SELECTOR = GuardianRecoveryValidator.proposeValidationKey.selector;

modifier onlyBootloader() {
require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Only bootloader can call this method");
// Continue execution if called from the bootloader.
_;
}

constructor(address aaFactoryAddress, address sessionKeyValidatorAddress) {
constructor(address aaFactoryAddress, address sessionKeyValidatorAddress, address accountRecoveryValidatorAddress) {
AA_FACTORY_CONTRACT_ADDRESS = aaFactoryAddress;
SESSION_KEY_VALIDATOR_CONTRACT_ADDRESS = sessionKeyValidatorAddress;
ACCOUNT_RECOVERY_VALIDATOR_CONTRACT_ADDRESS = accountRecoveryValidatorAddress;
}

function validateAndPayForPaymasterTransaction(
Expand All @@ -44,7 +49,9 @@ contract ExampleAuthServerPaymaster is IPaymaster, Ownable {
// Ensure the transaction is calling one of our allowed contracts
address to = address(uint160(_transaction.to));
require(
to == AA_FACTORY_CONTRACT_ADDRESS || to == SESSION_KEY_VALIDATOR_CONTRACT_ADDRESS,
to == AA_FACTORY_CONTRACT_ADDRESS ||
to == SESSION_KEY_VALIDATOR_CONTRACT_ADDRESS ||
to == ACCOUNT_RECOVERY_VALIDATOR_CONTRACT_ADDRESS,
"Unsupported contract address"
);

Expand All @@ -62,6 +69,12 @@ contract ExampleAuthServerPaymaster is IPaymaster, Ownable {
"Unsupported method"
);
}
if (to == SESSION_KEY_VALIDATOR_CONTRACT_ADDRESS) {
require(
methodSelector == ACCOUNT_RECOVERY_ADD_KEY_SELECTOR || methodSelector == ACCOUNT_RECOVERY_PROPOSE_KEY_SELECTOR,
"Unsupported method"
);
}

bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]);
require(paymasterInputSelector == IPaymasterFlow.general.selector, "Unsupported paymaster flow");
Expand Down
Loading
Loading