Skip to content

Commit b6511f9

Browse files
Merge pull request #282 from euler-xyz/integration-keyring
Add HookTargetAccessControlKeyring
2 parents 9be812e + e9fa4bd commit b6511f9

File tree

3 files changed

+870
-0
lines changed

3 files changed

+870
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# HookTargetAccessControlKeyring
2+
3+
## Overview
4+
5+
The `HookTargetAccessControlKeyring` is a specialized hook target contract that combines selector-based access control with keyring credential verification. This contract enables vault operators to restrict access to critical vault operations, ensuring only authorized users with valid credentials can perform operations on vaults.
6+
7+
## Purpose
8+
9+
The primary purpose of the `HookTargetAccessControlKeyring` is to provide a robust, dual-layer authentication system for EVK vaults:
10+
11+
1. **Selector-based access control** - Allows specific addresses to be whitelisted for particular function selectors
12+
2. **Keyring credential verification** - Requires users to possess valid credentials according to a specified policy
13+
14+
This approach creates a flexible security model where:
15+
- Privileged addresses can bypass credential checks when needed
16+
- Regular users must possess valid credentials to interact with the vault
17+
- Different types of operations can have tailored access requirements
18+
19+
## Components
20+
21+
The `HookTargetAccessControlKeyring` integrates several components:
22+
23+
- **BaseHookTarget** - Provides the foundation for intercepting vault operations
24+
- **SelectorAccessControl** - Provides role-based access control mapped to function selectors
25+
- **IKeyringCredentials** - External interface for credential verification
26+
27+
## Authentication Mechanism
28+
29+
The contract implements a sophisticated authentication workflow through the `_authenticateCallerAndAccount` function:
30+
31+
1. **Initial Role Check**:
32+
- First, check if the caller has either the `WILD_CARD` role or a specific role for the current function selector
33+
- If yes, bypass the keyring credential check entirely
34+
35+
2. **Caller Credential Check**:
36+
- Determine the EVC owner of the calling account
37+
- If no owner is registered, assume the caller itself is the owner
38+
- Verify the owner has valid credentials according to the specified policy ID
39+
40+
3. **Account Credential Check**:
41+
- Only performed if the account being operated on has a different EVC owner than the caller
42+
- Determine the EVC owner of the account being operated on
43+
- If no owner is registered, assume the account itself is the owner
44+
- Verify this owner also has valid credentials according to the policy ID
45+
46+
4. **Optimization**:
47+
- If the caller and the account being operated on share the same EVC owner, only one credential check is performed
48+
49+
This mechanism ensures that both the entity initiating the operation and the entity being affected by it (if different) have proper authorization.
50+
51+
## Hooked Operations
52+
53+
The contract intercepts and applies its authentication mechanism to the following vault operations:
54+
55+
1. **Asset Deposit/Mint Operations**:
56+
- `deposit(uint256, address receiver)`
57+
- `mint(uint256, address receiver)`
58+
- `skim(uint256, address receiver)`
59+
60+
2. **Asset Withdrawal/Redemption Operations**:
61+
- `withdraw(uint256, address, address owner)`
62+
- `redeem(uint256, address, address owner)`
63+
64+
3. **Borrowing/Repayment Operations**:
65+
- `borrow(uint256, address receiver)`
66+
- `repay(uint256, address receiver)`
67+
- `repayWithShares(uint256, address receiver)`
68+
- `pullDebt(uint256, address from)`
69+
70+
For each of these operations, the contract authenticates both the caller and the relevant account parameter (receiver or owner).
71+
72+
### Fallback Mechanism
73+
74+
The contract implements a fallback function that provides a catch-all authentication mechanism for any hooked operations that are not explicitly intercepted by the above functions.
75+
76+
When a hooked operation is called that doesn't match any of the explicitly defined functions, the fallback function:
77+
- Authenticates only the caller using the `_authenticateCaller` function
78+
- Applies the same role-based access control rules (WILD_CARD and selector-specific roles)
79+
- Does not perform keyring credential checks on any additional accounts
80+
81+
## Usage Patterns
82+
83+
### Installation Pattern
84+
85+
To use this hook target:
86+
87+
1. Deploy an instance of `HookTargetAccessControlKeyring`, specifying:
88+
- EVC address
89+
- Admin address
90+
- EVault factory address
91+
- Keyring contract address
92+
- Policy ID
93+
94+
2. Grant roles to addresses that should bypass credential checks
95+
96+
3. Install the hook target on the desired vault(s) and configure which operations should be hooked
97+
98+
## Example Scenario
99+
100+
A vault operator could use this hook target to create a vault cluster where:
101+
- Only users with valid credentials can perform operations on the vaults
102+
- Specific addresses (like chosen liquidators) are whitelisted to bypass credential checks
103+
104+
To achieve the above, the following operations should be hooked: `OP_DEPOSIT`, `OP_MINT`, `OP_WITHDRAW`, `OP_REDEEM`, `OP_SKIM`, `OP_BORROW`, `OP_REPAY`, `OP_REPAY_WITH_SHARES`, `OP_PULL_DEBT`, `OP_LIQUIDATE` and `OP_FLASHLOAN`.
105+
106+
The liquidators should be granted the `WILD_CARD` role so that they can liquidate and close the position without needing to obtain a Keyring credential.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
pragma solidity ^0.8.0;
4+
5+
import {BaseHookTarget} from "./BaseHookTarget.sol";
6+
import {SelectorAccessControl} from "../AccessControl/SelectorAccessControl.sol";
7+
8+
interface IKeyringCredentials {
9+
function checkCredential(address, uint32) external view returns (bool);
10+
}
11+
12+
/// @title HookTargetAccessControlKeyring
13+
/// @custom:security-contact security@euler.xyz
14+
/// @author Euler Labs (https://www.eulerlabs.com/)
15+
/// @notice Hook target contract that allows specific functions on the vault to be called only by authorized callers.
16+
contract HookTargetAccessControlKeyring is BaseHookTarget, SelectorAccessControl {
17+
/// @notice The Keyring contract used for credential checking
18+
IKeyringCredentials public immutable keyring;
19+
20+
/// @notice The policy ID used when checking credentials against the Keyring contract
21+
uint32 public immutable policyId;
22+
23+
/// @notice Initializes the HookTargetAccessControlKeyring contract
24+
/// @param _evc The address of the EVC.
25+
/// @param _admin The address to be granted the DEFAULT_ADMIN_ROLE.
26+
/// @param _eVaultFactory The address of the EVault factory.
27+
/// @param _keyring The address of the Keyring contract.
28+
/// @param _policyId The policy ID to be used for credential checking.
29+
constructor(address _evc, address _admin, address _eVaultFactory, address _keyring, uint32 _policyId)
30+
BaseHookTarget(_eVaultFactory)
31+
SelectorAccessControl(_evc, _admin)
32+
{
33+
keyring = IKeyringCredentials(_keyring);
34+
policyId = _policyId;
35+
}
36+
37+
/// @notice Fallback function to revert if the address is not whitelisted to call the selector.
38+
fallback() external {
39+
_authenticateCaller();
40+
}
41+
42+
/// @notice Intercepts EVault deposit operations to authenticate the caller and the receiver
43+
/// @param receiver The address that will receive shares
44+
function deposit(uint256, address receiver) external view {
45+
_authenticateCallerAndAccount(receiver);
46+
}
47+
48+
/// @notice Intercepts EVault mint operations to authenticate the caller and the receiver
49+
/// @param receiver The address that will receive shares
50+
function mint(uint256, address receiver) external view {
51+
_authenticateCallerAndAccount(receiver);
52+
}
53+
54+
/// @notice Intercepts EVault withdraw operations to authenticate the caller and the owner
55+
/// @param owner The address whose balance will change
56+
function withdraw(uint256, address, address owner) external view {
57+
_authenticateCallerAndAccount(owner);
58+
}
59+
60+
/// @notice Intercepts EVault redeem operations to authenticate the caller and the owner
61+
/// @param owner The address whose balance will change
62+
function redeem(uint256, address, address owner) external view {
63+
_authenticateCallerAndAccount(owner);
64+
}
65+
66+
/// @notice Intercepts EVault skim operations to authenticate the caller and the receiver
67+
/// @param receiver The address that will receive shares
68+
function skim(uint256, address receiver) external view {
69+
_authenticateCallerAndAccount(receiver);
70+
}
71+
72+
/// @notice Intercepts EVault borrow operations to authenticate the caller and the receiver
73+
/// @param receiver The address that will receive borrowed assets
74+
function borrow(uint256, address receiver) external view {
75+
_authenticateCallerAndAccount(receiver);
76+
}
77+
78+
/// @notice Intercepts EVault repay operations to authenticate the caller and the receiver
79+
/// @param receiver The address that will receive the repaid assets
80+
function repay(uint256, address receiver) external view {
81+
_authenticateCallerAndAccount(receiver);
82+
}
83+
84+
/// @notice Intercepts EVault repayWithShares operations to authenticate the caller and the receiver
85+
/// @param receiver The address that will receive the repaid assets
86+
function repayWithShares(uint256, address receiver) external view {
87+
_authenticateCallerAndAccount(receiver);
88+
}
89+
90+
/// @notice Intercepts EVault pullDebt operations to authenticate the caller and the from address
91+
/// @param from The address from which debt is being pulled
92+
function pullDebt(uint256, address from) external view {
93+
_authenticateCallerAndAccount(from);
94+
}
95+
96+
/// @notice Checks if the EVC owner of the account has valid Keyring credential
97+
/// @dev If the EVC owner is not registered yet, the account is assumed to be the owner
98+
/// @param account The address to check credential for
99+
/// @return bool True if the EVC owner of the account has valid Keyring credential
100+
function checkKeyringCredential(address account) external view returns (bool) {
101+
address owner = evc.getAccountOwner(account);
102+
return keyring.checkCredential(owner == address(0) ? account : owner, policyId);
103+
}
104+
105+
/// @notice Authenticates both the caller and the specified account for access control
106+
/// @dev This function checks if either the caller or the account owner are authorized to call the function
107+
/// @param account The account to be authenticated
108+
function _authenticateCallerAndAccount(address account) internal view {
109+
address caller = _msgSender();
110+
111+
// Skip Keyring authentication if caller has wildcard role or specific function selector role
112+
if (hasRole(WILD_CARD, caller) || hasRole(msg.sig, caller)) return;
113+
114+
address owner = evc.getAccountOwner(caller);
115+
116+
// If the EVC owner is not registered yet, assume the caller is the owner
117+
if (owner == address(0)) owner = caller;
118+
if (!keyring.checkCredential(owner, policyId)) revert NotAuthorized();
119+
120+
// If caller and account don't share the same EVC owner, authenticate the account separately
121+
if (!_haveCommonOwner(owner, account)) {
122+
owner = evc.getAccountOwner(account);
123+
124+
// If the EVC owner is not registered yet, assume the account is the owner
125+
if (owner == address(0)) owner = account;
126+
if (!keyring.checkCredential(owner, policyId)) revert NotAuthorized();
127+
}
128+
}
129+
130+
/// @notice Retrieves the message sender in the context of the EVC or calling vault.
131+
/// @dev If the caller is a vault deployed by the recognized EVault factory, this function extracts the real
132+
/// caller address from the calldata. Otherwise, this function returns the account on behalf of which the current
133+
/// operation is being performed, which is either msg.sender or the account authenticated by the EVC.
134+
/// @return The address of the message sender.
135+
function _msgSender() internal view virtual override (BaseHookTarget, SelectorAccessControl) returns (address) {
136+
address msgSender = BaseHookTarget._msgSender();
137+
return msg.sender == msgSender ? SelectorAccessControl._msgSender() : msgSender;
138+
}
139+
}

0 commit comments

Comments
 (0)