Skip to content
This repository has been archived by the owner on Dec 18, 2024. It is now read-only.

feat: Add session key module validator #16

Merged
merged 15 commits into from
Dec 17, 2024
13 changes: 3 additions & 10 deletions contracts/ClaveImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {Call} from './batch/BatchCaller.sol';

import {IClaveAccount} from './interfaces/IClave.sol';
import {AccountFactory} from './AccountFactory.sol';
import {BatchCaller} from './batch/BatchCaller.sol';

/**
* @title Main account contract for the Clave wallet infrastructure, forked for Abstract
Expand All @@ -37,21 +38,18 @@ contract ClaveImplementation is
ModuleManager,
ERC1271Handler,
TokenCallbackHandler,
BatchCaller,
IClaveAccount
{
// Helper library for the Transaction struct
using TransactionHelper for Transaction;
// Batch transaction helper contract
address private immutable _BATCH_CALLER;

uint256 public constant VERSION = 1;

/**
* @notice Constructor for the account implementation
* @param batchCaller address - Batch transaction helper contract
*/
constructor(address batchCaller) {
_BATCH_CALLER = batchCaller;
constructor() {
_disableInitializers();
}

Expand Down Expand Up @@ -298,11 +296,6 @@ contract ClaveImplementation is
revert(add(returnData, 0x20), size)
}
}
} else if (to == _BATCH_CALLER) {
bool success = EfficientCall.rawDelegateCall(gas, to, data);
if (!success && !allowFailure) {
EfficientCall.propagateRevert();
}
} else {
bool success = EfficientCall.rawCall(gas, to, value, data, false);
if (!success && !allowFailure) {
Expand Down
60 changes: 32 additions & 28 deletions contracts/batch/BatchCaller.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {SystemContractHelper} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractHelper.sol';
import {SystemContractsCaller} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol';
import {EfficientCall} from '@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol';
import { DEPLOYER_SYSTEM_CONTRACT } from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";
import {Errors} from '../libraries/Errors.sol';

import {SelfAuth} from '../auth/SelfAuth.sol';
// Each call data for batches
struct Call {
address target; // Target contract address
Expand All @@ -15,33 +16,36 @@ struct Call {

/// @title BatchCaller
/// @notice Make multiple calls in a single transaction
contract BatchCaller {
/// @notice Make multiple calls, ensure success if required
/// @dev Reverts if not called via delegatecall
/// @param calls Call[] calldata - An array of Call structs
function batchCall(Call[] calldata calls) external {
bool isDelegateCall = SystemContractHelper.getCodeAddress() != address(this);
if (!isDelegateCall) {
revert Errors.ONLY_DELEGATECALL();
}

// Execute each call
uint256 len = calls.length;
Call calldata calli;
for (uint256 i = 0; i < len; ) {
calli = calls[i];
address target = calli.target;
uint256 value = calli.value;
bytes calldata callData = calli.callData;
abstract contract BatchCaller is SelfAuth {
/// @notice Make multiple calls, ensure success if required.
/// @dev The total Ether sent across all calls must be equal to `msg.value` to maintain the invariant
/// that `msg.value` + `tx.fee` is the maximum amount of Ether that can be spent on the transaction.
/// @param _calls Array of Call structs, each representing an individual external call to be made.
function batchCall(Call[] calldata _calls) external payable onlySelf {
uint256 totalValue;
uint256 len = _calls.length;
for (uint256 i = 0; i < len; ++i) {
totalValue += _calls[i].value;
bool success;
if (_calls[i].target == address(DEPLOYER_SYSTEM_CONTRACT)) {
// Note, that the deployer contract can only be called with a "systemCall" flag.
success = SystemContractsCaller.systemCall(
uint32(gasleft()),
_calls[i].target,
_calls[i].value,
_calls[i].callData
);
} else {
success = EfficientCall.rawCall(gasleft(), _calls[i].target, _calls[i].value, _calls[i].callData, false);
}

bool success = EfficientCall.rawCall(gasleft(), target, value, callData, false);
if (!calls[i].allowFailure && !success) {
revert Errors.CALL_FAILED();
}
if (!_calls[i].allowFailure && !success) {
revert Errors.CALL_FAILED();
}
}

unchecked {
i++;
}
}
if (totalValue != msg.value) {
revert Errors.MsgValueMismatch(msg.value, totalValue);
}
}
}
3 changes: 3 additions & 0 deletions contracts/handlers/ValidationHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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';

/**
* @title ValidationHandler
Expand Down Expand Up @@ -54,6 +55,8 @@ abstract contract ValidationHandler is OwnerManager, ValidatorManager {
if (OwnerManager._k1IsOwner(recoveredAddress)) {
return true;
}
} else if (_isModuleValidator(validator)) {
return IModuleValidator(validator).handleValidation(signedHash, signature);
}

return false;
Expand Down
20 changes: 20 additions & 0 deletions contracts/helpers/TimestampAsserter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ITimestampAsserter} from "../interfaces/ITimestampAsserter.sol";

error TimestampOutOfRange(uint256 currentTimestamp, uint256 start, uint256 end);

/// @title TimestampAsserter
/// @author Matter Labs
/// @custom:security-contact [email protected]
/// @dev A contract that verifies if the current block timestamp falls within a specified range.
/// This is useful for custom account abstraction where time-bound checks are needed but accessing block.timestamp
/// directly is not possible.
contract TimestampAsserter is ITimestampAsserter {
function assertTimestampInRange(uint256 _start, uint256 _end) external view {
if (block.timestamp < _start || block.timestamp > _end) {
revert TimestampOutOfRange(block.timestamp, _start, _end);
}
}
}
22 changes: 22 additions & 0 deletions contracts/helpers/TimestampAsserterLocator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "../interfaces/ITimestampAsserter.sol";

library TimestampAsserterLocator {
function locate() internal view returns (ITimestampAsserter) {
if (block.chainid == 260) {
return ITimestampAsserter(address(0x00000000000000000000000000000000808012));
}
if (block.chainid == 11124) {
return ITimestampAsserter(address(0x27570660a298db7373EaA50c1a728DA93b5BC969));
}
if (block.chainid == 300) {
revert("Timestamp asserter is not deployed on ZKsync Sepolia testnet yet");
}
if (block.chainid == 324) {
revert("Timestamp asserter is not deployed on ZKsync mainnet yet");
}
revert("Timestamp asserter is not deployed on this network");
}
}
12 changes: 12 additions & 0 deletions contracts/interfaces/IModuleValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

/**
* @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);

function addValidationKey(bytes memory key) external returns (bool);
}
6 changes: 6 additions & 0 deletions contracts/interfaces/ITimestampAsserter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface ITimestampAsserter {
function assertTimestampInRange(uint256 start, uint256 end) external view;
}
41 changes: 41 additions & 0 deletions contracts/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,13 +36,27 @@ 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
* @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
* @param validator address - Address of the generic validator to add
* @param accountValidationKey bytes - data for the validator to use to validate the account
*/
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
Expand All @@ -59,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 @@ -73,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 @@ -84,4 +118,11 @@ 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);
}
3 changes: 2 additions & 1 deletion contracts/libraries/ClaveStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ library ClaveStorage {
// │ Validation │
mapping(address => address) r1Validators;
mapping(address => address) k1Validators;
uint256[50] __gap_2;
mapping(address => address) moduleValidators;
uint256[49] __gap_2;
// └───────────────────┘

// ┌───────────────────┐
Expand Down
2 changes: 2 additions & 0 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,6 @@ library Errors {

error MODULE_NOT_ADDED_CORRECTLY(); // 0xb66e8ec4
error MODULE_NOT_REMOVED_CORRECTLY(); // 0x680c8744

error MsgValueMismatch(uint256 actualValue, uint256 expectedValue);
}
Loading
Loading