@agenticprimitives/contracts 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUDIT.md +67 -0
- package/CLAUDE.md +40 -0
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/deployments-anvil.json +1 -0
- package/deployments-base-sepolia.json +1 -0
- package/dist/abi/AgentNameAttributeResolver.json +798 -0
- package/dist/abi/AgentNamePredicates.json +1 -0
- package/dist/abi/AgentNameRegistry.json +826 -0
- package/dist/abi/AgentNameUniversalResolver.json +222 -0
- package/dist/abi/AgentProfilePredicates.json +1 -0
- package/dist/abi/AgentProfileResolver.json +1044 -0
- package/dist/abi/AgentRelationship.json +583 -0
- package/dist/abi/AgentRelationshipPredicates.json +1 -0
- package/dist/abi/AgenticGovernance.json +259 -0
- package/dist/abi/AllowedMethodsEnforcer.json +108 -0
- package/dist/abi/AllowedTargetsEnforcer.json +103 -0
- package/dist/abi/ApprovedHashRegistry.json +114 -0
- package/dist/abi/AttributeStorage.json +557 -0
- package/dist/abi/CaveatEnforcerBase.json +130 -0
- package/dist/abi/GovernanceManaged.json +43 -0
- package/dist/abi/IAttributeReader.json +98 -0
- package/dist/abi/ICaveatEnforcer.json +98 -0
- package/dist/abi/IDelegationManager.json +211 -0
- package/dist/abi/IERC7579Module.json +34 -0
- package/dist/abi/IERC7579ModuleLifecycle.json +60 -0
- package/dist/abi/IGovernanceView.json +34 -0
- package/dist/abi/MultiSendCallOnly.json +29 -0
- package/dist/abi/MultiSendCallOnlyHarness.json +42 -0
- package/dist/abi/OntologyTermRegistry.json +397 -0
- package/dist/abi/P256Verifier.json +1 -0
- package/dist/abi/PermissionlessSubregistry.json +207 -0
- package/dist/abi/RelationshipTypeRegistry.json +455 -0
- package/dist/abi/ShapeRegistry.json +627 -0
- package/dist/abi/SmartAgentModuleTypes.json +1 -0
- package/dist/abi/TimestampEnforcer.json +108 -0
- package/dist/abi/ValueEnforcer.json +103 -0
- package/dist/abi/WebAuthnLib.json +1 -0
- package/dist/abi/index.d.ts +35 -0
- package/dist/abi/index.js +35 -0
- package/package.json +48 -0
- package/spec.md +52 -0
- package/src/AgentAccount.sol +1374 -0
- package/src/AgentAccountFactory.sol +274 -0
- package/src/ApprovedHashRegistry.sol +57 -0
- package/src/IAgentAccount.sol +138 -0
- package/src/SmartAgentPaymaster.sol +281 -0
- package/src/UniversalSignatureValidator.sol +136 -0
- package/src/agency/DelegationManager.sol +374 -0
- package/src/agency/ICaveatEnforcer.sol +62 -0
- package/src/agency/IDelegationManager.sol +69 -0
- package/src/custody/CustodyPolicy.sol +892 -0
- package/src/custody/IERC7579Module.sol +60 -0
- package/src/enforcers/AllowedMethodsEnforcer.AUDIT.md +51 -0
- package/src/enforcers/AllowedMethodsEnforcer.sol +48 -0
- package/src/enforcers/AllowedTargetsEnforcer.AUDIT.md +49 -0
- package/src/enforcers/AllowedTargetsEnforcer.sol +44 -0
- package/src/enforcers/CaveatEnforcerBase.sol +19 -0
- package/src/enforcers/QuorumEnforcer.AUDIT.md +71 -0
- package/src/enforcers/QuorumEnforcer.sol +191 -0
- package/src/enforcers/TimestampEnforcer.AUDIT.md +50 -0
- package/src/enforcers/TimestampEnforcer.sol +43 -0
- package/src/enforcers/ValueEnforcer.AUDIT.md +51 -0
- package/src/enforcers/ValueEnforcer.sol +41 -0
- package/src/governance/AgenticGovernance.sol +140 -0
- package/src/governance/GovernanceManaged.sol +75 -0
- package/src/governance/IGovernance.sol +15 -0
- package/src/identity/AgentProfilePredicates.sol +40 -0
- package/src/identity/AgentProfileResolver.sol +194 -0
- package/src/libraries/MultiSendCallOnly.sol +95 -0
- package/src/libraries/P256Verifier.sol +47 -0
- package/src/libraries/SignatureSlotRecovery.sol +196 -0
- package/src/libraries/WebAuthnLib.sol +164 -0
- package/src/naming/AgentNameAttributeResolver.sol +95 -0
- package/src/naming/AgentNamePredicates.sol +74 -0
- package/src/naming/AgentNameRegistry.sol +362 -0
- package/src/naming/AgentNameUniversalResolver.sol +210 -0
- package/src/naming/PermissionlessSubregistry.sol +98 -0
- package/src/ontology/AttributeStorage.sol +289 -0
- package/src/ontology/OntologyTermRegistry.sol +146 -0
- package/src/ontology/ShapeRegistry.sol +240 -0
- package/src/relationships/AgentRelationship.sol +289 -0
- package/src/relationships/AgentRelationshipPredicates.sol +44 -0
- package/src/relationships/RelationshipTypeRegistry.sol +143 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
5
|
+
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
|
6
|
+
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
|
7
|
+
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
|
|
8
|
+
import "./IDelegationManager.sol";
|
|
9
|
+
import "./ICaveatEnforcer.sol";
|
|
10
|
+
import "../governance/IGovernance.sol";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @title DelegationManager
|
|
14
|
+
* @notice On-chain delegation management with caveat enforcement.
|
|
15
|
+
*
|
|
16
|
+
* Aligned with ERC-7710 patterns and MetaMask delegation-framework design:
|
|
17
|
+
* 1. Delegator signs a Delegation struct via EIP-712
|
|
18
|
+
* 2. Delegate calls redeemDelegation() with the signed delegation chain
|
|
19
|
+
* 3. DelegationManager validates signatures and enforces all caveats (beforeHook/afterHook)
|
|
20
|
+
* 4. Execution goes through the delegator's smart account via execute()
|
|
21
|
+
*
|
|
22
|
+
* Spec 007 Phase A.5:
|
|
23
|
+
* - `redeemDelegation` is `nonReentrant` (SC5 § 6.2). Blocks nested
|
|
24
|
+
* redemption via caveat-enforcer or target-call callbacks.
|
|
25
|
+
* - `revokeDelegation(bytes32, bytes calldata)` is authenticated:
|
|
26
|
+
* either the delegator OR the delegate may revoke, with the
|
|
27
|
+
* verifier reading the delegation struct out of the explicit
|
|
28
|
+
* signature payload so Variant A (off-chain) delegations can be
|
|
29
|
+
* revoked too. C2 § 5 revocation-gap closure.
|
|
30
|
+
*
|
|
31
|
+
* Key ERC-7710 / DeleGator alignments:
|
|
32
|
+
* - Caveat args: redeemer-provided runtime arguments (excluded from delegation hash)
|
|
33
|
+
* - beforeHook/afterHook: enforcers revert on failure (no bool return)
|
|
34
|
+
* - Execute through delegator account, not direct target.call
|
|
35
|
+
* - Open delegations: delegate = address(0xa11) allows any redeemer
|
|
36
|
+
*/
|
|
37
|
+
contract DelegationManager is IDelegationManager, ReentrancyGuard {
|
|
38
|
+
using ECDSA for bytes32;
|
|
39
|
+
using MessageHashUtils for bytes32;
|
|
40
|
+
|
|
41
|
+
// ─── H7-C.10 / EXT3-010 — system-wide pause hook ───────────────────
|
|
42
|
+
//
|
|
43
|
+
// DelegationManager is a singleton — every redeem of every delegation
|
|
44
|
+
// on every chain flows through this contract. An incident-mode pause
|
|
45
|
+
// here is the kill-switch that stops new delegation actions while an
|
|
46
|
+
// exploit is being investigated. The pause flag is sourced from
|
|
47
|
+
// `AgenticGovernance.isPaused()` (a guardian can pause without delay;
|
|
48
|
+
// unpause needs the timelock).
|
|
49
|
+
//
|
|
50
|
+
// `governance` is `address(0)` for legacy deploys; in that case the
|
|
51
|
+
// pause check is skipped (no governance, no pause source). Production
|
|
52
|
+
// deploys pass a real AgenticGovernance address.
|
|
53
|
+
address public immutable governance;
|
|
54
|
+
|
|
55
|
+
error SystemPaused();
|
|
56
|
+
|
|
57
|
+
/// @dev Root authority constant — delegations with this authority are root-level
|
|
58
|
+
bytes32 public constant ROOT_AUTHORITY = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
|
|
59
|
+
|
|
60
|
+
/// @dev Open delegation sentinel — any address can redeem
|
|
61
|
+
address public constant OPEN_DELEGATION = address(0xa11);
|
|
62
|
+
|
|
63
|
+
/// @dev EIP-712 domain separator
|
|
64
|
+
bytes32 public immutable DOMAIN_SEPARATOR;
|
|
65
|
+
|
|
66
|
+
/// @dev Delegation type hash for EIP-712.
|
|
67
|
+
///
|
|
68
|
+
/// **R1 / CROSS-STACK-001 closure (2026-05-31):** converged to
|
|
69
|
+
/// STANDARD EIP-712. Previously used the non-standard inline form
|
|
70
|
+
/// `Delegation(...,bytes32 caveatsHash,...)` which produced a
|
|
71
|
+
/// different typehash from what off-chain `viem.hashTypedData`
|
|
72
|
+
/// derives from a `Caveat[]` field. The standard form encodes
|
|
73
|
+
/// `Caveat[] caveats` in the type string + appends the `Caveat(...)`
|
|
74
|
+
/// type definition.
|
|
75
|
+
///
|
|
76
|
+
/// The `_hashCaveats` function below already computes
|
|
77
|
+
/// `keccak256(concat(hashStruct(c) for c in caveats))` which IS the
|
|
78
|
+
/// EIP-712 standard `encodeData(Caveat[])`. structHash computation
|
|
79
|
+
/// stays identical; only the typehash STRING changed.
|
|
80
|
+
bytes32 public constant DELEGATION_TYPEHASH = keccak256(
|
|
81
|
+
"Delegation(address delegator,address delegate,bytes32 authority,Caveat[] caveats,uint256 salt)Caveat(address enforcer,bytes terms)"
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
/// @dev Caveat type hash for EIP-712 (only enforcer + terms; args excluded).
|
|
85
|
+
bytes32 public constant CAVEAT_TYPEHASH = keccak256(
|
|
86
|
+
"Caveat(address enforcer,bytes terms)"
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
/// @dev Revoked delegation hashes
|
|
90
|
+
mapping(bytes32 => bool) private _revoked;
|
|
91
|
+
|
|
92
|
+
/// @notice Emitted when a revocation succeeds with the address that
|
|
93
|
+
/// submitted it (delegator or delegate).
|
|
94
|
+
event DelegationRevokedBy(bytes32 indexed delegationHash, address indexed by);
|
|
95
|
+
|
|
96
|
+
error DelegationRevoked_();
|
|
97
|
+
error DelegationManager_InvalidSignature();
|
|
98
|
+
error InvalidAuthority();
|
|
99
|
+
error OnlyDelegator();
|
|
100
|
+
error ExecutionFailed();
|
|
101
|
+
error InvalidDelegate();
|
|
102
|
+
error EmptyChain();
|
|
103
|
+
|
|
104
|
+
// Phase A.5 errors.
|
|
105
|
+
error NotDelegatorOrDelegate();
|
|
106
|
+
error HashMismatch();
|
|
107
|
+
/// @dev Thrown by the deprecated permissionless `revokeDelegation(bytes32)`
|
|
108
|
+
/// path. Migrate callers to `revokeDelegationByOwner(Delegation)`.
|
|
109
|
+
error LegacyRevocationDisabled();
|
|
110
|
+
|
|
111
|
+
/// @dev Storage gap reserves 50 slots for future state. The contract
|
|
112
|
+
/// is currently NOT upgradeable (SC4 § 4.3.4 — DelegationManager
|
|
113
|
+
/// is singleton + re-deploy on bug), but the gap standardises
|
|
114
|
+
/// our storage discipline and keeps the option open.
|
|
115
|
+
uint256[50] private __gap;
|
|
116
|
+
|
|
117
|
+
/// @notice Deploys the DelegationManager. Pass `address(0)` for
|
|
118
|
+
/// `governance_` to opt out of the H7-C.10 pause gate
|
|
119
|
+
/// (legacy / test deploys). Production deploys pass an
|
|
120
|
+
/// `AgenticGovernance` address.
|
|
121
|
+
constructor(address governance_) {
|
|
122
|
+
governance = governance_;
|
|
123
|
+
DOMAIN_SEPARATOR = keccak256(
|
|
124
|
+
abi.encode(
|
|
125
|
+
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
|
|
126
|
+
keccak256("AgentDelegationManager"),
|
|
127
|
+
keccak256("1"),
|
|
128
|
+
block.chainid,
|
|
129
|
+
address(this)
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// @inheritdoc IDelegationManager
|
|
135
|
+
/// @dev Phase A.5 (SC5 § 6.2) — `nonReentrant` blocks nested
|
|
136
|
+
/// redemption (a caveat enforcer or target call cannot
|
|
137
|
+
/// reenter this function).
|
|
138
|
+
function redeemDelegation(
|
|
139
|
+
Delegation[] calldata delegations,
|
|
140
|
+
address target,
|
|
141
|
+
uint256 value,
|
|
142
|
+
bytes calldata data
|
|
143
|
+
) external nonReentrant {
|
|
144
|
+
// H7-C.10 / EXT3-010: system-wide pause gate. Skipped when
|
|
145
|
+
// `governance` is `address(0)`, an EOA, or a non-conforming
|
|
146
|
+
// contract (legacy / test deploys). Production deploys pass an
|
|
147
|
+
// `AgenticGovernance` address; a guardian can pause without delay;
|
|
148
|
+
// unpause requires the 24h timelock.
|
|
149
|
+
if (governance != address(0) && governance.code.length > 0) {
|
|
150
|
+
(bool ok, bytes memory data) = governance.staticcall(
|
|
151
|
+
abi.encodeWithSelector(IGovernanceView.isPaused.selector)
|
|
152
|
+
);
|
|
153
|
+
if (ok && data.length >= 32 && abi.decode(data, (bool))) revert SystemPaused();
|
|
154
|
+
}
|
|
155
|
+
if (delegations.length == 0) revert EmptyChain();
|
|
156
|
+
|
|
157
|
+
// Phase 1: Validate chain + run beforeHooks (leaf to root)
|
|
158
|
+
for (uint256 i = 0; i < delegations.length; i++) {
|
|
159
|
+
_validateDelegation(delegations, i);
|
|
160
|
+
_runBeforeHooks(delegations[i], target, value, data);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Phase 2: Execute through the root delegator's smart account
|
|
164
|
+
address rootDelegator = delegations[delegations.length - 1].delegator;
|
|
165
|
+
_executeFromDelegator(rootDelegator, target, value, data);
|
|
166
|
+
|
|
167
|
+
// Phase 3: After-hooks (root to leaf, per DeleGator convention)
|
|
168
|
+
for (uint256 i = delegations.length; i > 0; i--) {
|
|
169
|
+
_runAfterHooks(delegations[i - 1], target, value, data);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// @inheritdoc IDelegationManager
|
|
174
|
+
/// @notice DEPRECATED — permissionless revocation by hash.
|
|
175
|
+
/// @dev Production-readiness pass: the permissionless legacy path is a
|
|
176
|
+
/// DoS surface — a mempool observer or any party who reconstructs
|
|
177
|
+
/// a Variant A delegation hash can mark it revoked. The original
|
|
178
|
+
/// rationale (Variant B delegations register on-chain so the
|
|
179
|
+
/// hash is already public) doesn't hold for Variant A, which is
|
|
180
|
+
/// now the default.
|
|
181
|
+
///
|
|
182
|
+
/// We retain the function selector for ABI compatibility with
|
|
183
|
+
/// existing tooling, but always revert with `LegacyRevocationDisabled()`.
|
|
184
|
+
/// Callers MUST migrate to `revokeDelegationByOwner(Delegation)`
|
|
185
|
+
/// which authenticates `msg.sender` against the delegation struct.
|
|
186
|
+
function revokeDelegation(bytes32 /* delegationHash */) external pure {
|
|
187
|
+
revert LegacyRevocationDisabled();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// @notice Phase A.5 — authenticated revocation path that works for
|
|
191
|
+
/// Variant A (off-chain caveated delegation, never
|
|
192
|
+
/// registered) by deriving the hash from the delegation
|
|
193
|
+
/// struct + signature provided by the caller.
|
|
194
|
+
///
|
|
195
|
+
/// @dev Authorization gate: `msg.sender` MUST be either
|
|
196
|
+
/// `delegation.delegator` OR `delegation.delegate` (the latter
|
|
197
|
+
/// blocks a random EOA from revoking a delegation it doesn't
|
|
198
|
+
/// hold, which would otherwise be a permissionless DoS vector
|
|
199
|
+
/// against the legacy `revokeDelegation(bytes32)` path).
|
|
200
|
+
///
|
|
201
|
+
/// @param delegation The delegation to revoke. The signature inside
|
|
202
|
+
/// it is verified to confirm the struct is
|
|
203
|
+
/// legitimate before we mark the hash revoked —
|
|
204
|
+
/// otherwise a malicious delegate could revoke a
|
|
205
|
+
/// *forged* delegation hash by submitting a struct
|
|
206
|
+
/// they invented.
|
|
207
|
+
function revokeDelegationByOwner(Delegation calldata delegation) external {
|
|
208
|
+
if (msg.sender != delegation.delegator && msg.sender != delegation.delegate) {
|
|
209
|
+
revert NotDelegatorOrDelegate();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
bytes32 dHash = hashDelegation(delegation);
|
|
213
|
+
|
|
214
|
+
// Verify the delegation struct is signed by its declared
|
|
215
|
+
// delegator. We don't want a delegate to be able to revoke
|
|
216
|
+
// a hash that was never a real delegation.
|
|
217
|
+
_validateSignature(delegation.delegator, dHash, delegation.signature);
|
|
218
|
+
|
|
219
|
+
_revoked[dHash] = true;
|
|
220
|
+
emit DelegationRevoked(dHash);
|
|
221
|
+
emit DelegationRevokedBy(dHash, msg.sender);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// @inheritdoc IDelegationManager
|
|
225
|
+
function isRevoked(bytes32 delegationHash) external view returns (bool) {
|
|
226
|
+
return _revoked[delegationHash];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// @notice Compute the EIP-712 hash of a delegation.
|
|
230
|
+
function hashDelegation(Delegation calldata d) public view returns (bytes32) {
|
|
231
|
+
bytes32 caveatsHash = _hashCaveats(d.caveats);
|
|
232
|
+
bytes32 structHash = keccak256(
|
|
233
|
+
abi.encode(
|
|
234
|
+
DELEGATION_TYPEHASH,
|
|
235
|
+
d.delegator,
|
|
236
|
+
d.delegate,
|
|
237
|
+
d.authority,
|
|
238
|
+
caveatsHash,
|
|
239
|
+
d.salt
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── Internal: Validation ──────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
function _validateDelegation(
|
|
248
|
+
Delegation[] calldata delegations,
|
|
249
|
+
uint256 i
|
|
250
|
+
) internal {
|
|
251
|
+
Delegation calldata d = delegations[i];
|
|
252
|
+
bytes32 dHash = hashDelegation(d);
|
|
253
|
+
|
|
254
|
+
// Check not revoked
|
|
255
|
+
if (_revoked[dHash]) revert DelegationRevoked_();
|
|
256
|
+
|
|
257
|
+
// Validate delegate
|
|
258
|
+
if (i == 0) {
|
|
259
|
+
if (d.delegate != OPEN_DELEGATION && d.delegate != msg.sender) revert InvalidDelegate();
|
|
260
|
+
} else {
|
|
261
|
+
if (d.delegate != OPEN_DELEGATION && d.delegate != delegations[i - 1].delegator) revert InvalidDelegate();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Validate authority chain
|
|
265
|
+
if (d.authority != ROOT_AUTHORITY) {
|
|
266
|
+
if (i + 1 >= delegations.length) revert InvalidAuthority();
|
|
267
|
+
bytes32 parentHash = hashDelegation(delegations[i + 1]);
|
|
268
|
+
if (d.authority != parentHash) revert InvalidAuthority();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Validate signature
|
|
272
|
+
_validateSignature(d.delegator, dHash, d.signature);
|
|
273
|
+
|
|
274
|
+
emit DelegationRedeemed(dHash, d.delegator, d.delegate);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ─── Internal: Caveat Hooks ────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
function _runBeforeHooks(
|
|
280
|
+
Delegation calldata d,
|
|
281
|
+
address target,
|
|
282
|
+
uint256 value,
|
|
283
|
+
bytes calldata data
|
|
284
|
+
) internal {
|
|
285
|
+
bytes32 dHash = hashDelegation(d);
|
|
286
|
+
for (uint256 j = 0; j < d.caveats.length; j++) {
|
|
287
|
+
ICaveatEnforcer(d.caveats[j].enforcer).beforeHook(
|
|
288
|
+
d.caveats[j].terms,
|
|
289
|
+
d.caveats[j].args,
|
|
290
|
+
dHash,
|
|
291
|
+
d.delegator,
|
|
292
|
+
msg.sender,
|
|
293
|
+
target,
|
|
294
|
+
value,
|
|
295
|
+
data
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function _runAfterHooks(
|
|
301
|
+
Delegation calldata d,
|
|
302
|
+
address target,
|
|
303
|
+
uint256 value,
|
|
304
|
+
bytes calldata data
|
|
305
|
+
) internal {
|
|
306
|
+
bytes32 dHash = hashDelegation(d);
|
|
307
|
+
for (uint256 j = 0; j < d.caveats.length; j++) {
|
|
308
|
+
ICaveatEnforcer(d.caveats[j].enforcer).afterHook(
|
|
309
|
+
d.caveats[j].terms,
|
|
310
|
+
d.caveats[j].args,
|
|
311
|
+
dHash,
|
|
312
|
+
d.delegator,
|
|
313
|
+
msg.sender,
|
|
314
|
+
target,
|
|
315
|
+
value,
|
|
316
|
+
data
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ─── Internal: Execution ───────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
function _executeFromDelegator(
|
|
324
|
+
address delegator,
|
|
325
|
+
address target,
|
|
326
|
+
uint256 value,
|
|
327
|
+
bytes calldata data
|
|
328
|
+
) internal {
|
|
329
|
+
// Call the delegator account's execute(address,uint256,bytes) function
|
|
330
|
+
// This ensures msg.sender in the target contract is the delegator
|
|
331
|
+
(bool success, bytes memory returnData) = delegator.call(
|
|
332
|
+
abi.encodeWithSignature("execute(address,uint256,bytes)", target, value, data)
|
|
333
|
+
);
|
|
334
|
+
if (!success) {
|
|
335
|
+
if (returnData.length > 0) {
|
|
336
|
+
assembly {
|
|
337
|
+
revert(add(returnData, 32), mload(returnData))
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
revert ExecutionFailed();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── Internal: Signature ───────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
function _validateSignature(
|
|
347
|
+
address signer,
|
|
348
|
+
bytes32 digest,
|
|
349
|
+
bytes calldata signature
|
|
350
|
+
) internal view {
|
|
351
|
+
// ERC-1271 for smart accounts
|
|
352
|
+
if (signer.code.length > 0) {
|
|
353
|
+
bytes4 result = IERC1271(signer).isValidSignature(digest, signature);
|
|
354
|
+
if (result != IERC1271.isValidSignature.selector) revert DelegationManager_InvalidSignature();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// EOA — recover from eth-signed message hash
|
|
358
|
+
bytes32 ethHash = digest.toEthSignedMessageHash();
|
|
359
|
+
address recovered = ethHash.recover(signature);
|
|
360
|
+
if (recovered != signer) revert DelegationManager_InvalidSignature();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ─── Internal: Hashing ─────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
function _hashCaveats(Caveat[] calldata caveats) internal pure returns (bytes32) {
|
|
366
|
+
bytes32[] memory hashes = new bytes32[](caveats.length);
|
|
367
|
+
for (uint256 i = 0; i < caveats.length; i++) {
|
|
368
|
+
hashes[i] = keccak256(
|
|
369
|
+
abi.encode(CAVEAT_TYPEHASH, caveats[i].enforcer, keccak256(caveats[i].terms))
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return keccak256(abi.encodePacked(hashes));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title ICaveatEnforcer
|
|
6
|
+
* @notice Interface for caveat enforcers following MetaMask DeleGator patterns.
|
|
7
|
+
*
|
|
8
|
+
* Enforcers validate delegation constraints using a beforeHook/afterHook pattern.
|
|
9
|
+
* They REVERT on failure rather than returning a bool — this aligns with
|
|
10
|
+
* the MetaMask delegation-framework and ERC-7710 conventions.
|
|
11
|
+
*
|
|
12
|
+
* @dev terms: Immutable parameters set when the delegation is created (e.g., time window, allowed methods).
|
|
13
|
+
* args: Mutable parameters provided by the redeemer at redemption time (e.g., proof data).
|
|
14
|
+
*/
|
|
15
|
+
interface ICaveatEnforcer {
|
|
16
|
+
/**
|
|
17
|
+
* @notice Called before the delegated action is executed.
|
|
18
|
+
* MUST revert if the caveat is not satisfied.
|
|
19
|
+
* @param terms Encoded parameters set at delegation creation time.
|
|
20
|
+
* @param args Encoded parameters provided at redemption time.
|
|
21
|
+
* @param delegationHash Hash of the delegation being redeemed.
|
|
22
|
+
* @param delegator The account that created the delegation.
|
|
23
|
+
* @param redeemer The address calling redeemDelegation.
|
|
24
|
+
* @param target The target contract being called.
|
|
25
|
+
* @param value The ETH value being sent.
|
|
26
|
+
* @param callData The calldata for the target call.
|
|
27
|
+
*/
|
|
28
|
+
function beforeHook(
|
|
29
|
+
bytes calldata terms,
|
|
30
|
+
bytes calldata args,
|
|
31
|
+
bytes32 delegationHash,
|
|
32
|
+
address delegator,
|
|
33
|
+
address redeemer,
|
|
34
|
+
address target,
|
|
35
|
+
uint256 value,
|
|
36
|
+
bytes calldata callData
|
|
37
|
+
) external;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @notice Called after the delegated action is executed.
|
|
41
|
+
* MUST revert if post-execution state is invalid.
|
|
42
|
+
* Not all enforcers need post-hooks — default is a no-op.
|
|
43
|
+
* @param terms Encoded parameters set at delegation creation time.
|
|
44
|
+
* @param args Encoded parameters provided at redemption time.
|
|
45
|
+
* @param delegationHash Hash of the delegation being redeemed.
|
|
46
|
+
* @param delegator The account that created the delegation.
|
|
47
|
+
* @param redeemer The address calling redeemDelegation.
|
|
48
|
+
* @param target The target contract being called.
|
|
49
|
+
* @param value The ETH value being sent.
|
|
50
|
+
* @param callData The calldata for the target call.
|
|
51
|
+
*/
|
|
52
|
+
function afterHook(
|
|
53
|
+
bytes calldata terms,
|
|
54
|
+
bytes calldata args,
|
|
55
|
+
bytes32 delegationHash,
|
|
56
|
+
address delegator,
|
|
57
|
+
address redeemer,
|
|
58
|
+
address target,
|
|
59
|
+
uint256 value,
|
|
60
|
+
bytes calldata callData
|
|
61
|
+
) external;
|
|
62
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title IDelegationManager
|
|
6
|
+
* @notice Manages delegations between agent accounts with caveat enforcement.
|
|
7
|
+
*
|
|
8
|
+
* Aligned with ERC-7710 (Smart Contract Delegation) patterns and inspired by
|
|
9
|
+
* MetaMask delegation-framework:
|
|
10
|
+
* - A delegation grants a delegate the right to act on behalf of a delegator
|
|
11
|
+
* - Caveats constrain what the delegate can do (time, value, methods, targets)
|
|
12
|
+
* - Delegations can be chained: A delegates to B, B delegates to C
|
|
13
|
+
* - Revocation is immediate and on-chain
|
|
14
|
+
* - Execution goes through the delegator's smart account (executeFromExecutor)
|
|
15
|
+
*
|
|
16
|
+
* ERC-7710 defines: redeemDelegations(bytes[], bytes32[], bytes[])
|
|
17
|
+
* We provide both the typed `redeemDelegation` and the ERC-7710 opaque interface.
|
|
18
|
+
*/
|
|
19
|
+
interface IDelegationManager {
|
|
20
|
+
struct Caveat {
|
|
21
|
+
address enforcer; // contract that validates this caveat
|
|
22
|
+
bytes terms; // encoded parameters set at delegation creation time
|
|
23
|
+
bytes args; // encoded parameters provided by redeemer at redemption time
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
struct Delegation {
|
|
27
|
+
address delegator; // account granting authority
|
|
28
|
+
address delegate; // account receiving authority (address(0xa11) = open delegation)
|
|
29
|
+
bytes32 authority; // parent delegation hash (ROOT_AUTHORITY for root)
|
|
30
|
+
Caveat[] caveats; // restrictions on the delegation
|
|
31
|
+
uint256 salt; // replay protection
|
|
32
|
+
bytes signature; // EIP-712 signature from delegator
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// @notice Emitted when a delegation is redeemed.
|
|
36
|
+
event DelegationRedeemed(
|
|
37
|
+
bytes32 indexed delegationHash,
|
|
38
|
+
address indexed delegator,
|
|
39
|
+
address indexed delegate
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/// @notice Emitted when a delegation is revoked.
|
|
43
|
+
event DelegationRevoked(bytes32 indexed delegationHash);
|
|
44
|
+
|
|
45
|
+
/// @notice Redeem a delegation chain to execute an action on behalf of the delegator.
|
|
46
|
+
/// The delegator's smart account executes the call via executeFromExecutor.
|
|
47
|
+
function redeemDelegation(
|
|
48
|
+
Delegation[] calldata delegations,
|
|
49
|
+
address target,
|
|
50
|
+
uint256 value,
|
|
51
|
+
bytes calldata data
|
|
52
|
+
) external;
|
|
53
|
+
|
|
54
|
+
/// @notice Revoke a delegation by its hash.
|
|
55
|
+
///
|
|
56
|
+
/// @dev Legacy permissionless path. Phase A.5 introduces
|
|
57
|
+
/// `revokeDelegationByOwner` for authenticated revocation that
|
|
58
|
+
/// works for Variant A delegations.
|
|
59
|
+
function revokeDelegation(bytes32 delegationHash) external;
|
|
60
|
+
|
|
61
|
+
/// @notice Phase A.5 — authenticated revocation. Caller must be
|
|
62
|
+
/// either `delegation.delegator` or `delegation.delegate`.
|
|
63
|
+
/// The delegation struct is signature-checked first to
|
|
64
|
+
/// prevent a malicious delegate from revoking a forged hash.
|
|
65
|
+
function revokeDelegationByOwner(Delegation calldata delegation) external;
|
|
66
|
+
|
|
67
|
+
/// @notice Check if a delegation has been revoked.
|
|
68
|
+
function isRevoked(bytes32 delegationHash) external view returns (bool);
|
|
69
|
+
}
|