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

Session key module #99

Merged
merged 39 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9437387
feat: new session key module compiled
ly0va Oct 16, 2024
731073d
test: add some tests
ly0va Oct 16, 2024
1fd3731
test: forgot to push
ly0va Oct 16, 2024
9ea2a3a
fix: session key transaction
ly0va Oct 17, 2024
5a0c423
fix: install / uninstall
ly0va Oct 17, 2024
6feded2
fix: a bunch of bugs
ly0va Oct 17, 2024
bc05b52
feat: create mini test framework
ly0va Oct 18, 2024
c1fff2a
feat: big refactor and ERC20 tests
ly0va Oct 19, 2024
e4f922f
feat: more tests
ly0va Oct 20, 2024
d84713b
fix: reorder tests
ly0va Oct 20, 2024
0b84192
feat: add getters
ly0va Oct 21, 2024
b68ec0e
fix: ERC165 support
ly0va Oct 21, 2024
c135f4e
feat: properly separate calls and transfers
ly0va Oct 22, 2024
8acedfe
fix: better fee limits
ly0va Oct 22, 2024
ab9eb0b
feat: if -> require
ly0va Oct 23, 2024
42d54dd
feat: storage layout overhaul to comply with AA restrictions
ly0va Oct 23, 2024
75c7813
feat: store number of constraints
ly0va Oct 24, 2024
9e66f62
feat: make tests work using local node
ly0va Oct 25, 2024
4122801
test: revoke key and getters
ly0va Oct 25, 2024
1621a7a
chore: format and lint
ly0va Oct 28, 2024
8ed637b
Merge remote-tracking branch 'origin/main' into lyova-session-keys-mo…
ly0va Oct 28, 2024
adc1efd
feat: session state getter
ly0va Oct 28, 2024
8961895
feat: try to adapt sdk and gateway to the new interface
ly0va Oct 29, 2024
dfa3428
chore: format
ly0va Oct 29, 2024
3c18e57
fix: update addresses
ly0va Oct 29, 2024
b794dc0
fix: erc165
ly0va Oct 29, 2024
044e6f0
fix: pass hooks data
ly0va Oct 30, 2024
e30fac9
fix: serialize bigints in a message
ly0va Oct 30, 2024
2658c92
chore: split e2e and contract tests
ly0va Oct 30, 2024
ea6c590
fix: update addresses
ly0va Oct 30, 2024
7069b20
chore: fix lints
ly0va Oct 30, 2024
76bda4f
chore: cspell fix
ly0va Oct 30, 2024
6ab5a04
fix: update address
ly0va Oct 30, 2024
86d5df4
fix: erc165 again
ly0va Oct 30, 2024
ebb927a
fix: review
ly0va Oct 31, 2024
903acbd
docs: update docs
ly0va Oct 31, 2024
8a606f6
chore: fmt
ly0va Oct 31, 2024
84ec928
feat: add comments and refactor getters a bit
ly0va Oct 31, 2024
1a5d231
Merge branch 'main' into lyova-session-keys-module
MexicanAce Oct 31, 2024
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
50 changes: 42 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:

jobs:
contracts:
e2e:
runs-on: ubuntu-latest
defaults:
run:
Expand Down Expand Up @@ -40,13 +40,9 @@ jobs:
- name: Build SDK
run: pnpm nx build sdk

# Build contracts and generate types
- name: Build contracts
run: pnpm nx build contracts

# Run contract tests
- name: Run contract test
run: pnpm nx test contracts
# Deploy contracts
- name: Deploy contracts
run: pnpm nx deploy contracts

# Run E2E tests
- name: Install Playwright Browsers
Expand All @@ -60,3 +56,41 @@ jobs:
name: playwright-report
path: packages/demo-app/playwright-report/
retention-days: 3


contracts:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

# Start node
- name: Era Test Node Action
uses: dutterbutter/era-test-node-action@36ffd2eefd46dc16e7e2a8e1715124400ec0a3ba # v1

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

- name: Use Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
node-version: lts/Iron
cache: 'pnpm'

# Install dependencies for contracts/gateway/sdk
- name: Install dependencies
run: pnpm install -r --frozen-lockfile

# Build contracts and generate types
- name: Build contracts
run: pnpm nx build contracts

# Run contract tests
- name: Run contract test
run: pnpm nx test contracts

34 changes: 18 additions & 16 deletions docs/sdk/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,18 @@ development principles in mind.
2. Deploy the account

```ts
import { generatePrivateKey, privateKeyToAccount } from "viem";
import { generatePrivateKey, privateKeyToAddress } from "viem";
import { deployAccount } from "zksync-account/client";

const deployerClient = ...; // Any client for deploying the account, make sure it has enough balance to cover the deployment cost
const sessionKey = generatePrivateKey();
const sessionPublicKey = privateKeyToAccount(sessionKey.value).address;
const sessionPrivateKey = generatePrivateKey();
const sessionKey = privateKeyToAddress(sessionPrivateKey.value);

const { address } = await deployAccount(deployerClient, {
credentialPublicKey,
initialSessions: [
{
sessionPublicKey,
expiresAt: (new Date(Date.now() + 1000 * 60 * 60 * 24)).toISOString(), // 1 day expiry
spendLimit: {
[Token.address]: "1000",
},
},
],
// You can either create a session during deployment by passing a spec
// here, or create it later using `createSession` -- see step 4.
// initialSession: { ... },
contracts,
});
```
Expand Down Expand Up @@ -73,9 +67,17 @@ development principles in mind.
4. Activating session key

```ts
await passkeyClient.addSessionKey({
sessionPublicKey,
token: Token.address, // Address of the token
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 1 day expiry
await passkeyClient.createSession({
session: {
sessionKey,
expiry: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, // 1 week
feeLimit: { limit: parseEther("0.01") },
transferPolicies: [
{
target: transferTarget.address,
maxValuePerUse: parseEther("0.1"),
},
],
},
});
```
8 changes: 3 additions & 5 deletions packages/contracts/src/ClaveAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,14 @@ contract ClaveAccount is
bytes32 suggestedSignedHash,
Transaction calldata transaction
) external payable override onlyBootloader returns (bytes4 magic) {
// FIXME session txs have their own nonce managers,
ly0va marked this conversation as resolved.
Show resolved Hide resolved
// so they have to not alter this nonce
_incrementNonce(transaction.nonce);

// The fact there is enough balance for the account
// should be checked explicitly to prevent user paying for fee for a
// transaction that wouldn't be included on Ethereum.
if (transaction.totalRequiredBalance() > address(this).balance) {
Logger.logString("revert Errors.INSUFFICIENT_FUNDS()");
revert Errors.INSUFFICIENT_FUNDS();
}

Expand Down Expand Up @@ -209,13 +210,10 @@ contract ClaveAccount is
bytes32 signedHash,
Transaction calldata transaction
) internal returns (bytes4 magicValue) {
Logger.logString("_validateTransaction");
if (transaction.signature.length == 65) {
Logger.logString("transaction.signature.length == 65");
(address signer, ) = ECDSA.tryRecover(signedHash, transaction.signature);
Logger.logString("recovered signer");
Logger.logString("recovered EOA signer");
Logger.logAddress(signer);
// gas estimation?
if (signer == address(0)) {
return bytes4(0);
}
Expand Down
32 changes: 32 additions & 0 deletions packages/contracts/src/interfaces/IValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ interface IValidatorManager {
*/
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
Expand All @@ -30,6 +36,12 @@ interface IValidatorManager {
*/
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
Expand Down Expand Up @@ -67,6 +79,13 @@ interface IValidatorManager {
*/
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
* @param validator address - Address of the validator to remove
*/
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
Expand All @@ -81,6 +100,13 @@ interface IValidatorManager {
*/
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
* @return True if the address is a validator, false otherwise
*/
function isModuleValidator(address validator) external view returns (bool);

/**
* @notice Returns the list of r1 validators
* @return validatorList address[] memory - Array of r1 validator addresses
Expand All @@ -92,4 +118,10 @@ interface IValidatorManager {
* @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
*/
function listModuleValidators() external view returns (address[] memory validatorList);
}
9 changes: 3 additions & 6 deletions packages/contracts/src/managers/ModuleManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import { IModuleManager } from "../interfaces/IModuleManager.sol";
import { IUserOpValidator } from "../interfaces/IERC7579Validator.sol";
import { IERC7579Module, IExecutor } from "../interfaces/IERC7579Module.sol";

import "../helpers/Logger.sol";

/**
* @title Manager contract for modules
* @notice Abstract contract for managing the enabled modules of the account
Expand Down Expand Up @@ -66,10 +64,7 @@ abstract contract ModuleManager is IModuleManager, Auth {

function _addNativeModule(address moduleAddress, bytes memory moduleData) internal {
if (!_supportsModule(moduleAddress)) {
Logger.logString("module is not supported");
Logger.logAddress(moduleAddress);
// FIXME: support native modules on install
// revert Errors.MODULE_ERC165_FAIL();
revert Errors.MODULE_ERC165_FAIL();
}

_modulesLinkedList().add(moduleAddress);
Expand Down Expand Up @@ -173,6 +168,8 @@ abstract contract ModuleManager is IModuleManager, Auth {
}

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);
}
}
31 changes: 31 additions & 0 deletions packages/contracts/src/managers/ValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ abstract contract ValidatorManager is IValidatorManager, Auth {
_k1RemoveValidator(validator);
}

///@inheritdoc IValidatorManager
function removeModuleValidator(address validator) external onlySelfOrModule {
_removeModuleValidator(validator);
}

/// @inheritdoc IValidatorManager
function r1IsValidator(address validator) external view override returns (bool) {
return _r1IsValidator(validator);
Expand All @@ -57,6 +62,11 @@ abstract contract ValidatorManager is IValidatorManager, Auth {
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();
Expand All @@ -67,6 +77,11 @@ abstract contract ValidatorManager is IValidatorManager, Auth {
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();
Expand All @@ -78,8 +93,14 @@ abstract contract ValidatorManager is IValidatorManager, Auth {
}

function _addModuleValidator(address validator, bytes memory accountValidationKey) internal {
if (!_supportsModuleValidator(validator)) {
revert Errors.VALIDATOR_ERC165_FAIL();
}

_moduleValidatorsLinkedList().add(validator);
IModuleValidator(validator).addValidationKey(accountValidationKey);

emit AddModuleValidator(validator);
}

function _k1AddValidator(address validator) internal {
Expand Down Expand Up @@ -108,6 +129,12 @@ abstract contract ValidatorManager is IValidatorManager, Auth {
emit K1RemoveValidator(validator);
}

function _removeModuleValidator(address validator) internal {
_moduleValidatorsLinkedList().remove(validator);

emit RemoveModuleValidator(validator);
}

function _r1IsValidator(address validator) internal view returns (bool) {
return _r1ValidatorsLinkedList().exists(validator);
}
Expand All @@ -128,6 +155,10 @@ abstract contract ValidatorManager is IValidatorManager, Auth {
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 = ClaveStorage.layout().r1Validators;
}
Expand Down
10 changes: 10 additions & 0 deletions packages/contracts/src/test/ERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestERC20 is ERC20 {
constructor(address mintTo) ERC20("Test ERC20", "TEST") {
_mint(mintTo, 10 ** 18);
}
}
3 changes: 2 additions & 1 deletion packages/contracts/src/validators/PasskeyValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.24;

import { Base64 } from "../helpers/Base64.sol";
import { IR1Validator, IERC165 } from "../interfaces/IValidator.sol";
import { IModule } from "../interfaces/IModule.sol";
import { Errors } from "../libraries/Errors.sol";
import { VerifierCaller } from "../helpers/VerifierCaller.sol";
import { JsmnSolLib } from "../libraries/JsmnSolLib.sol";
Expand Down Expand Up @@ -41,7 +42,7 @@ contract PasskeyValidator is IR1Validator, VerifierCaller {
}

/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
return interfaceId == type(IR1Validator).interfaceId || interfaceId == type(IERC165).interfaceId;
}

Expand Down
Loading