Skip to content
Open
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
3 changes: 3 additions & 0 deletions contracts/ClaimIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
import { Errors } from "./libraries/Errors.sol";
import { KeyPurposes } from "./libraries/KeyPurposes.sol";

contract ClaimIssuer is IClaimIssuer, Identity {

Check warning on line 9 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @notice tag in contract 'ClaimIssuer'

Check warning on line 9 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @author tag in contract 'ClaimIssuer'

Check warning on line 9 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @title tag in contract 'ClaimIssuer'
mapping(bytes => bool) public revokedClaims;

Check warning on line 10 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @notice tag in variable 'revokedClaims'

/**
* @notice Constructor for direct deployments (non-proxy)
* @param initialManagementKey The initial management key for the ClaimIssuer
*/
// solhint-disable-next-line no-empty-blocks
constructor(

Check warning on line 17 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Mismatch in @param names for function '<anonymous>'. Expected: [initialManagementKey], Found: []

Check warning on line 17 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @param tag in function '<anonymous>'

Check warning on line 17 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @notice tag in function '<anonymous>'
address initialManagementKey
) Identity(initialManagementKey, false) {}

Expand All @@ -32,7 +32,7 @@
/**
* @dev See {IClaimIssuer-revokeClaimBySignature}.
*/
function revokeClaimBySignature(

Check warning on line 35 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Mismatch in @param names for function 'revokeClaimBySignature'. Expected: [signature], Found: []

Check warning on line 35 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @param tag in function 'revokeClaimBySignature'

Check warning on line 35 in contracts/ClaimIssuer.sol

View workflow job for this annotation

GitHub Actions / Lint sources (20.x)

Missing @notice tag in function 'revokeClaimBySignature'
bytes calldata signature
) external override delegatedOnly onlyManager {
require(!revokedClaims[signature], Errors.ClaimAlreadyRevoked());
Expand Down Expand Up @@ -144,6 +144,9 @@
// Initialize UUPS upgradeability
__UUPSUpgradeable_init();

// Initialize IdentitySmartAccount functionality
__IdentitySmartAccount_init();

// Initialize Identity functionality
__Identity_init(initialManagementKey);

Expand Down
139 changes: 137 additions & 2 deletions contracts/Identity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@ import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { IdentitySmartAccount } from "./IdentitySmartAccount.sol";
import {
SIG_VALIDATION_FAILED,
SIG_VALIDATION_SUCCESS
} from "@account-abstraction/contracts/core/Helpers.sol";
import { PackedUserOperation } from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import { IEntryPoint } from "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { Exec } from "@account-abstraction/contracts/utils/Exec.sol";
import { IIdentity } from "./interface/IIdentity.sol";
import { IClaimIssuer } from "./interface/IClaimIssuer.sol";
import { IERC734 } from "./interface/IERC734.sol";
import { IERC735 } from "./interface/IERC735.sol";
import { Version } from "./version/Version.sol";
import { Errors } from "./libraries/Errors.sol";
import { KeyPurposes } from "./libraries/KeyPurposes.sol";
import { KeyTypes } from "./libraries/KeyTypes.sol";
import { Structs } from "./storage/Structs.sol";
import { KeyManager } from "./KeyManager.sol";

Expand Down Expand Up @@ -41,6 +49,7 @@ contract Identity is
Initializable,
UUPSUpgradeable,
IIdentity,
IdentitySmartAccount,
Version,
KeyManager,
MulticallUpgradeable
Expand Down Expand Up @@ -112,6 +121,44 @@ contract Identity is
}
}

/**
* @dev See {IERC734-execute}.
* @notice Executes a single call from the account (ERC-734 compatible)
*/
function execute(
address _to,
uint256 _value,
bytes calldata _data
)
external
payable
virtual
override(IERC734, KeyManager)
returns (uint256 executionId)
{
// Allow entry point calls
if (msg.sender == address(entryPoint())) {
// For entry point calls, use direct execution
_executeDirect(_to, _value, _data);
return 0; // Return 0 for entry point calls
}

// For regular calls, use KeyManager's execution logic
KeyStorage storage ks = _getKeyStorage();
executionId = ks.executionNonce;
ks.executions[executionId].to = _to;
ks.executions[executionId].value = _value;
ks.executions[executionId].data = _data;
ks.executionNonce++;

emit ExecutionRequested(executionId, _to, _value, _data);
Comment on lines +140 to +154
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execute() does two different things

if called by EntryPoint runs directly adn if called by anyone else it goes through 734 flow
I think is kinda messy can be confusing maybe separate functions or force EntryPoint to use executeBatch


// Check if execution can be auto-approved
if (_canAutoApproveExecution(_to)) {
_approve(executionId, true);
}
}

/**
* @notice When using this contract as an implementation for a proxy, call this initializer with a delegatecall.
* @dev This function initializes the upgradeable contract and sets up the initial management key.
Expand All @@ -123,6 +170,7 @@ contract Identity is
) external virtual initializer {
require(initialManagementKey != address(0), Errors.ZeroAddress());
__UUPSUpgradeable_init();
__IdentitySmartAccount_init();
__Identity_init(initialManagementKey);
__Version_init("2.2.2");
}
Expand Down Expand Up @@ -438,7 +486,12 @@ contract Identity is
*/
function _authorizeUpgrade(
address newImplementation
) internal virtual override onlyManager {
)
internal
virtual
override(IdentitySmartAccount, UUPSUpgradeable)
onlyManager
{
// Only management keys can authorize upgrades
// This prevents unauthorized upgrades and potential rug pulls
}
Expand Down Expand Up @@ -554,6 +607,88 @@ contract Identity is
}
}

/**
* @dev Internal function for direct execution (used by entry point)
* @param _to The target address
* @param _value The value to send
* @param _data The calldata
*
* @notice Uses the professional Exec library pattern from BaseAccount for consistent
* and gas-optimized execution with proper error handling.
*/
function _executeDirect(
address _to,
uint256 _value,
bytes calldata _data
) internal {
bool ok = Exec.call(_to, _value, _data, gasleft());
if (!ok) {
Exec.revertWithReturnData();
}
}

/**
* @dev See {IdentitySmartAccount-_requireManager}.
* @notice Requires the caller to have management permissions
*/
function _requireManager() internal view override onlyManager {
// The onlyManager modifier handles the access control
}

/**
* @dev See {IdentitySmartAccount-_validateSignature}.
* @notice Validates the signature of a UserOperation and the signer's permissions
* This function performs complete validation:
* 1. Recovers the signer address from the signature
* 2. Validates that the signature is valid (not address(0))
* 3. Validates that the signer has required permissions (ERC4337_SIGNER or MANAGEMENT)
* @param userOp The UserOperation to validate
* @param userOpHash The hash of the UserOperation
* @return validationData Packed validation data (0 for success, 1 for signature/permission failure)
*/
function _validateSignature(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your signature validation just returns 0 ro 1
in 4337 you can also return validAfter and validUntil (time window for sigs)
useful for expiring or delayed ops
not a blocker but nice to have if you plan to support smart scheduling

PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal view override returns (uint256 validationData) {
// 1. Recover the signer address from the signature
address signer = ECDSA.recover(userOpHash, userOp.signature);

// 2. Validate that the signature is valid
if (signer == address(0)) {
return SIG_VALIDATION_FAILED;
}

// 3. Validate that the signer has required permissions
if (
!keyHasPurpose(
keccak256(abi.encode(signer)),
KeyPurposes.ERC4337_SIGNER
) &&
!keyHasPurpose(
keccak256(abi.encode(signer)),
KeyPurposes.MANAGEMENT
)
) {
return SIG_VALIDATION_FAILED;
}

return SIG_VALIDATION_SUCCESS;
}

/**
* @dev See {IdentitySmartAccount-_requireForExecute}.
* @notice Requires the caller to be authorized for execution
*/
function _requireForExecute() internal view override {
// Allow entry point calls
if (msg.sender == address(entryPoint())) {
return;
}

// For all other calls, require management permissions
_requireManager();
}

/**
* @dev Internal helper to validate claim with external issuer.
*
Expand Down
Loading