@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,51 @@
|
|
|
1
|
+
# `ValueEnforcer` — Security & Architecture Audit
|
|
2
|
+
|
|
3
|
+
**Status:** shipped
|
|
4
|
+
**Last refreshed:** 2026-05-21
|
|
5
|
+
**Owners:** delegation package CODEOWNERS
|
|
6
|
+
**Registry entry:** [`docs/architecture/enforcer-registry/enforcers.json`](../../../../docs/architecture/enforcer-registry/enforcers.json) (entry: `ValueEnforcer`)
|
|
7
|
+
**Live address (Base Sepolia):** `0x49F0B31bf5228B1964dED8DC0F357f104cA74523`
|
|
8
|
+
|
|
9
|
+
## 1. Charter
|
|
10
|
+
|
|
11
|
+
Caps the native-token (`msg.value`) sent in a redemption to at most `maxValue` wei. Per-call, not cumulative. The redeemer cannot exceed — enforcer reverts.
|
|
12
|
+
|
|
13
|
+
Does NOT do: cumulative-across-redemptions value caps (DTK splits this into a separate enforcer; we don't have it yet — phase 8). Does not cover ERC-20 token amounts (those live in calldata; phase 6c.6 `ArgumentRuleEnforcer` covers them).
|
|
14
|
+
|
|
15
|
+
## 2. Security invariants
|
|
16
|
+
|
|
17
|
+
- `maxValue` is set at delegation creation, immutable.
|
|
18
|
+
- The redemption's `value` parameter is checked exactly: `require(value <= maxValue)`.
|
|
19
|
+
- `maxValue = 0` is permitted (no native-token transfers allowed via this delegation).
|
|
20
|
+
- The check has no slack / decimal logic — bare wei comparison.
|
|
21
|
+
|
|
22
|
+
## 3. Threat model
|
|
23
|
+
|
|
24
|
+
| Threat | Likelihood | Impact | Mitigation | Status |
|
|
25
|
+
| --- | --- | --- | --- | --- |
|
|
26
|
+
| Multiple redemptions add up to a value exceeding intent | High by design | High | Per-call only; the delegator MUST add a usage-counter caveat (planned RateLimitEnforcer) for cumulative caps | **Open: VE-1** — cumulative variant tracked as gap |
|
|
27
|
+
| Native-token forwarding from target to other recipients | Low | Medium | Out-of-scope here — the enforcer doesn't trace where the target sends the value. Use `AllowedTargetsEnforcer` to restrict who receives. | By design |
|
|
28
|
+
| `maxValue` set very high accidentally | Low | High | SDK lint should warn above 1 ETH; AgentAccount's `t3HighValueCeiling` provides a second backstop | **Open: VE-2** — lint pending |
|
|
29
|
+
|
|
30
|
+
## 4. DTK / smart-agent parity
|
|
31
|
+
|
|
32
|
+
**DTK:** `ValueLteEnforcer` + `NativeTokenLimitEnforcer` split. Our single `ValueEnforcer` matches `ValueLteEnforcer` semantically. We lack the cumulative variant — phase 8.
|
|
33
|
+
|
|
34
|
+
**smart-agent:** `ValueEnforcer.sol` — same shape.
|
|
35
|
+
|
|
36
|
+
## 5. Test posture
|
|
37
|
+
|
|
38
|
+
Forge tests: `packages/contracts/test/Enforcers.t.sol` covers value=maxValue / value<maxValue / value>maxValue / value=0 cases (4 tests).
|
|
39
|
+
|
|
40
|
+
## 6. Open findings
|
|
41
|
+
|
|
42
|
+
| ID | Severity | Finding | Status |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| VE-1 | P2 | No cumulative-value enforcer yet; intent leakage if delegator doesn't pair with RateLimitEnforcer. | Open (phase 8) |
|
|
45
|
+
| VE-2 | P3 | SDK lint warning for high `maxValue` (> 1 ETH) absent. | Open |
|
|
46
|
+
|
|
47
|
+
## 7. Cross-references
|
|
48
|
+
|
|
49
|
+
- [Registry entry](../../../../docs/architecture/enforcer-registry/enforcers.json)
|
|
50
|
+
- Spec 207 § 6 — T3 high-value gate that informs how T3 (value) delegations should always carry a ValueEnforcer.
|
|
51
|
+
- Phase 8 follow-up: cumulative-value variant.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import "../agency/ICaveatEnforcer.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title ValueEnforcer
|
|
8
|
+
* @notice Enforces a maximum ETH value per call.
|
|
9
|
+
* @dev terms = abi.encode(uint256 maxValue)
|
|
10
|
+
* Follows ERC-7710 / MetaMask DeleGator beforeHook/afterHook pattern.
|
|
11
|
+
*/
|
|
12
|
+
contract ValueEnforcer is ICaveatEnforcer {
|
|
13
|
+
error ValueExceedsLimit();
|
|
14
|
+
|
|
15
|
+
function beforeHook(
|
|
16
|
+
bytes calldata terms,
|
|
17
|
+
bytes calldata,
|
|
18
|
+
bytes32,
|
|
19
|
+
address,
|
|
20
|
+
address,
|
|
21
|
+
address,
|
|
22
|
+
uint256 value,
|
|
23
|
+
bytes calldata
|
|
24
|
+
) external pure override {
|
|
25
|
+
uint256 maxValue = abi.decode(terms, (uint256));
|
|
26
|
+
if (value > maxValue) revert ValueExceedsLimit();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function afterHook(
|
|
30
|
+
bytes calldata,
|
|
31
|
+
bytes calldata,
|
|
32
|
+
bytes32,
|
|
33
|
+
address,
|
|
34
|
+
address,
|
|
35
|
+
address,
|
|
36
|
+
uint256,
|
|
37
|
+
bytes calldata
|
|
38
|
+
) external pure override {
|
|
39
|
+
// No post-execution check needed
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import "./IGovernance.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title AgenticGovernance
|
|
8
|
+
* @notice H7-C.9 / EXT3-009 closure — the governance surface every
|
|
9
|
+
* `GovernanceManaged` contract (`AgentAccountFactory`,
|
|
10
|
+
* `SmartAgentPaymaster`, the registries) sees as its
|
|
11
|
+
* `governance` immutable. Wraps:
|
|
12
|
+
*
|
|
13
|
+
* - **Pause / unpause** (the kill-switch behind every
|
|
14
|
+
* `whenNotPaused` modifier in `GovernanceManaged`).
|
|
15
|
+
* - **Forwarded execution** (`execute(target, data, value)`)
|
|
16
|
+
* so the slow-path timelock can deliver `onlyGovernance`
|
|
17
|
+
* calls that authenticate as `msg.sender == address(this)`.
|
|
18
|
+
* - **Signer / pauser registry** (`isSigner`) consumed by
|
|
19
|
+
* `IGovernanceView`.
|
|
20
|
+
*
|
|
21
|
+
* Two-role authority split:
|
|
22
|
+
* - `timelock` — the OZ `TimelockController(24h)` deployed
|
|
23
|
+
* alongside this contract. Owns slow / deliberate actions
|
|
24
|
+
* (admin role mutations, unpause, signer changes,
|
|
25
|
+
* forwarded calls). All actions go through 24h delay.
|
|
26
|
+
* - `guardian` — fast-path emergency pause. CAN pause (no
|
|
27
|
+
* delay; the incident-response lever) but CANNOT unpause —
|
|
28
|
+
* unpause requires the timelock so an attacker who steals
|
|
29
|
+
* guardian keys can't unpause a paused system.
|
|
30
|
+
*
|
|
31
|
+
* Post-deploy operator step (see deploy-runbook): replace the
|
|
32
|
+
* bootstrap `[deployer]` proposers/executors on the timelock
|
|
33
|
+
* with a long-lived multisig (an `AgentAccount` deployed by
|
|
34
|
+
* the factory whose `CustodyPolicy` requires M-of-N signers).
|
|
35
|
+
* Deployer then renounces the timelock admin role and is gone.
|
|
36
|
+
*/
|
|
37
|
+
contract AgenticGovernance is IGovernanceView {
|
|
38
|
+
// ─── Immutables ───────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/// @notice The `TimelockController(24h)` that may execute slow-path
|
|
41
|
+
/// governance actions through this contract. Set once at
|
|
42
|
+
/// construction; rotate by redeploying.
|
|
43
|
+
address public immutable timelock;
|
|
44
|
+
|
|
45
|
+
/// @notice Fast-path emergency-pauser role. Can pause without delay
|
|
46
|
+
/// but CANNOT unpause (unpause requires the timelock).
|
|
47
|
+
address public immutable guardian;
|
|
48
|
+
|
|
49
|
+
// ─── State ────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
bool private _paused;
|
|
52
|
+
mapping(address => bool) private _signers;
|
|
53
|
+
|
|
54
|
+
// ─── Errors ───────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
error NotTimelock(address caller);
|
|
57
|
+
error NotGuardian(address caller);
|
|
58
|
+
error ExecuteFailed(address target, bytes returndata);
|
|
59
|
+
error ZeroAddress();
|
|
60
|
+
|
|
61
|
+
// ─── Events ───────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
event Paused(address indexed by);
|
|
64
|
+
event Unpaused(address indexed by);
|
|
65
|
+
event SignerSet(address indexed who, bool isSigner);
|
|
66
|
+
event Executed(address indexed target, uint256 value, bytes data);
|
|
67
|
+
|
|
68
|
+
// ─── Constructor ──────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
constructor(address timelock_, address guardian_, address[] memory initialSigners) {
|
|
71
|
+
if (timelock_ == address(0) || guardian_ == address(0)) revert ZeroAddress();
|
|
72
|
+
timelock = timelock_;
|
|
73
|
+
guardian = guardian_;
|
|
74
|
+
for (uint256 i; i < initialSigners.length; i++) {
|
|
75
|
+
address s = initialSigners[i];
|
|
76
|
+
if (s == address(0)) revert ZeroAddress();
|
|
77
|
+
_signers[s] = true;
|
|
78
|
+
emit SignerSet(s, true);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── IGovernanceView ──────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/// @inheritdoc IGovernanceView
|
|
85
|
+
function isPaused() external view returns (bool) {
|
|
86
|
+
return _paused;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// @inheritdoc IGovernanceView
|
|
90
|
+
function isSigner(address who) external view returns (bool) {
|
|
91
|
+
return _signers[who];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Pause / Unpause ──────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
/// @notice Emergency pause. Callable by the guardian without delay.
|
|
97
|
+
function pause() external {
|
|
98
|
+
if (msg.sender != guardian) revert NotGuardian(msg.sender);
|
|
99
|
+
_paused = true;
|
|
100
|
+
emit Paused(msg.sender);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// @notice Unpause. Requires the timelock (a deliberate 24h
|
|
104
|
+
/// decision); guardian alone cannot unpause.
|
|
105
|
+
function unpause() external {
|
|
106
|
+
if (msg.sender != timelock) revert NotTimelock(msg.sender);
|
|
107
|
+
_paused = false;
|
|
108
|
+
emit Unpaused(msg.sender);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Slow-path governance (timelock only) ─────────────────────
|
|
112
|
+
|
|
113
|
+
/// @notice Forward a call to a `GovernanceManaged` target. The
|
|
114
|
+
/// target sees `msg.sender == address(this)`, satisfying
|
|
115
|
+
/// its `onlyGovernance` modifier. Only callable by the
|
|
116
|
+
/// timelock (every action sees a 24h delay before execute).
|
|
117
|
+
function execute(address target, bytes calldata data, uint256 value)
|
|
118
|
+
external
|
|
119
|
+
payable
|
|
120
|
+
returns (bytes memory)
|
|
121
|
+
{
|
|
122
|
+
if (msg.sender != timelock) revert NotTimelock(msg.sender);
|
|
123
|
+
(bool ok, bytes memory result) = target.call{value: value}(data);
|
|
124
|
+
if (!ok) revert ExecuteFailed(target, result);
|
|
125
|
+
emit Executed(target, value, data);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// @notice Add / remove a signer (consumed by `isSigner` reads from
|
|
130
|
+
/// downstream `GovernanceManaged` contracts).
|
|
131
|
+
function setSigner(address who, bool sig) external {
|
|
132
|
+
if (msg.sender != timelock) revert NotTimelock(msg.sender);
|
|
133
|
+
_signers[who] = sig;
|
|
134
|
+
emit SignerSet(who, sig);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Receive ETH ──────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
receive() external payable {}
|
|
140
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import "./IGovernance.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @title GovernanceManaged
|
|
8
|
+
* @notice Base contract that gates admin functions behind a single
|
|
9
|
+
* `Governance` multisig + timelock, and exposes a pause hook so
|
|
10
|
+
* downstream writes can be killed system-wide in an incident.
|
|
11
|
+
*
|
|
12
|
+
* Spec 007 Phase A.5 (SC4 § 4.2). The `governance` address is
|
|
13
|
+
* immutable per deploy — to "upgrade" governance for a contract,
|
|
14
|
+
* you redeploy the contract pointing at the new governance.
|
|
15
|
+
* Governance itself is non-upgradeable.
|
|
16
|
+
*
|
|
17
|
+
* Inheritors gain:
|
|
18
|
+
* - `onlyGovernance` modifier for setters / admin functions.
|
|
19
|
+
* - `whenNotPaused` modifier for write-surface functions that
|
|
20
|
+
* should freeze during an incident.
|
|
21
|
+
* - A pause-aware view (`paused()`) for off-chain tooling.
|
|
22
|
+
*/
|
|
23
|
+
abstract contract GovernanceManaged {
|
|
24
|
+
/// @notice The Governance multisig + timelock contract that owns
|
|
25
|
+
/// admin authority over this contract.
|
|
26
|
+
address public immutable governance;
|
|
27
|
+
|
|
28
|
+
error NotGovernance();
|
|
29
|
+
error SystemPaused();
|
|
30
|
+
error ZeroGovernance();
|
|
31
|
+
|
|
32
|
+
constructor(address governance_) {
|
|
33
|
+
if (governance_ == address(0)) revert ZeroGovernance();
|
|
34
|
+
governance = governance_;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// @dev Only the governance contract (executing a passed proposal)
|
|
38
|
+
/// can call functions protected by this modifier.
|
|
39
|
+
modifier onlyGovernance() {
|
|
40
|
+
if (msg.sender != governance) revert NotGovernance();
|
|
41
|
+
_;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// @dev Reverts when the governance pause flag is set. Read-only
|
|
45
|
+
/// functions stay live; this is for write-surface protection
|
|
46
|
+
/// only.
|
|
47
|
+
///
|
|
48
|
+
/// H7-C.10: skipped when `governance` is an EOA or a contract
|
|
49
|
+
/// that doesn't implement `IGovernanceView` (legacy / test
|
|
50
|
+
/// deploys). Production deploys MUST pass an
|
|
51
|
+
/// `AgenticGovernance` contract; the production-deploy
|
|
52
|
+
/// preflight (`check:production-deploy`) enforces that.
|
|
53
|
+
modifier whenNotPaused() {
|
|
54
|
+
if (_pausedSafe()) revert SystemPaused();
|
|
55
|
+
_;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// @notice Read the global pause flag. Returns false when governance
|
|
59
|
+
/// is non-conforming.
|
|
60
|
+
function paused() external view returns (bool) {
|
|
61
|
+
return _pausedSafe();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// @dev Calls `governance.isPaused()` via staticcall and treats any
|
|
65
|
+
/// failure (EOA, missing function, revert) as "not paused" so
|
|
66
|
+
/// legacy / test deploys with a non-conforming governance still
|
|
67
|
+
/// work. Production deploys ALWAYS use `AgenticGovernance` which
|
|
68
|
+
/// implements `IGovernanceView`.
|
|
69
|
+
function _pausedSafe() internal view returns (bool) {
|
|
70
|
+
if (governance.code.length == 0) return false;
|
|
71
|
+
(bool ok, bytes memory data) = governance.staticcall(abi.encodeWithSelector(IGovernanceView.isPaused.selector));
|
|
72
|
+
if (!ok || data.length < 32) return false;
|
|
73
|
+
return abi.decode(data, (bool));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
/// @title IGovernanceView
|
|
5
|
+
/// @notice Minimal read surface every `GovernanceManaged` contract depends
|
|
6
|
+
/// on. The pause flag flows through here so we can short-circuit
|
|
7
|
+
/// writes without pulling in the whole governance type.
|
|
8
|
+
interface IGovernanceView {
|
|
9
|
+
/// @notice Global system-pause flag.
|
|
10
|
+
function isPaused() external view returns (bool);
|
|
11
|
+
|
|
12
|
+
/// @notice Whether `who` is currently authorised to call `emergencyPause`
|
|
13
|
+
/// on behalf of governance (i.e. they're an active signer).
|
|
14
|
+
function isSigner(address who) external view returns (bool);
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title AgentProfilePredicates
|
|
6
|
+
* @notice Canonical bytes32 ids for identity-profile predicates,
|
|
7
|
+
* registered in the shared OntologyTermRegistry alongside
|
|
8
|
+
* the AgentName predicates from NS Phase 3.
|
|
9
|
+
*
|
|
10
|
+
* Some predicates are SHARED with AgentName (`atl:displayName`,
|
|
11
|
+
* `atl:metadataURI`, `atl:metadataHash`, `atl:agentKind`) — same
|
|
12
|
+
* vocabulary across stores is the whole point of the central
|
|
13
|
+
* OntologyTermRegistry. The IDs here that overlap MUST equal the
|
|
14
|
+
* AgentName ones (they're both `keccak256("atl:displayName")` etc.).
|
|
15
|
+
*
|
|
16
|
+
* Identity-only predicates (`atl:description`, `atl:homepage`,
|
|
17
|
+
* `atl:avatar`, `atl:profileSchemaURI`, `atl:profileActive`,
|
|
18
|
+
* `atl:profileRegisteredAt`) are NEW and registered by Deploy.s.sol.
|
|
19
|
+
*/
|
|
20
|
+
library AgentProfilePredicates {
|
|
21
|
+
// ─── Shared with AgentName (MUST equal AgentNamePredicates.*) ────
|
|
22
|
+
|
|
23
|
+
bytes32 internal constant ATL_DISPLAY_NAME = keccak256("atl:displayName");
|
|
24
|
+
bytes32 internal constant ATL_AGENT_KIND = keccak256("atl:agentKind");
|
|
25
|
+
bytes32 internal constant ATL_METADATA_URI = keccak256("atl:metadataURI");
|
|
26
|
+
bytes32 internal constant ATL_METADATA_HASH = keccak256("atl:metadataHash");
|
|
27
|
+
|
|
28
|
+
// ─── Identity-only ──────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
bytes32 internal constant ATL_DESCRIPTION = keccak256("atl:description");
|
|
31
|
+
bytes32 internal constant ATL_HOMEPAGE = keccak256("atl:homepage");
|
|
32
|
+
bytes32 internal constant ATL_AVATAR = keccak256("atl:avatar");
|
|
33
|
+
bytes32 internal constant ATL_PROFILE_SCHEMA_URI = keccak256("atl:profileSchemaURI");
|
|
34
|
+
bytes32 internal constant ATL_PROFILE_ACTIVE = keccak256("atl:profileActive");
|
|
35
|
+
bytes32 internal constant ATL_PROFILE_REGISTERED_AT = keccak256("atl:profileRegisteredAt");
|
|
36
|
+
|
|
37
|
+
// ─── Class id ───────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
bytes32 internal constant CLASS_AGENT_PROFILE = keccak256("atl:AgentProfile");
|
|
40
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import "../ontology/AttributeStorage.sol";
|
|
5
|
+
import "./AgentProfilePredicates.sol";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @title AgentProfileResolver
|
|
9
|
+
* @notice Per-agent profile resolver. Inherits the shared
|
|
10
|
+
* AttributeStorage so writes are predicate-active-checked
|
|
11
|
+
* against the OntologyTermRegistry deployed in NS Phase 3.
|
|
12
|
+
*
|
|
13
|
+
* Subject encoding: `bytes32(uint256(uint160(agent)))`. This places
|
|
14
|
+
* the agent's address in the low 20 bytes of the bytes32 subject id
|
|
15
|
+
* — same convention smart-agent uses, lets the SDK convert
|
|
16
|
+
* mechanically between `address` and `subject` without lookup.
|
|
17
|
+
*
|
|
18
|
+
* Authorization: `msg.sender == agent`. The agent's Smart Agent
|
|
19
|
+
* executes through its CustodyPolicy gate, then calls into this
|
|
20
|
+
* contract — `msg.sender` here equals the agent. No isOwner fallback
|
|
21
|
+
* (our AgentAccount has no built-in owner registry; quorum belongs
|
|
22
|
+
* in the CustodyPolicy module).
|
|
23
|
+
*
|
|
24
|
+
* Adapted from smart-agent
|
|
25
|
+
* (`packages/contracts/src/AgentAccountResolver.sol`, 274 LOC) with
|
|
26
|
+
* the following simplifications per spec 217 § Phase 3 + ADR-0007:
|
|
27
|
+
*
|
|
28
|
+
* - Single auth path (`msg.sender == agent`) — no isOwner staticcall.
|
|
29
|
+
* - Profile shape (`atl:AgentProfile`) defined in ShapeRegistry,
|
|
30
|
+
* not via a hand-written `CoreRecord` struct here.
|
|
31
|
+
* - Off-chain `AgentCard` JSON is the source of truth; this
|
|
32
|
+
* resolver stores the content-hash anchor + typed metadata only.
|
|
33
|
+
*/
|
|
34
|
+
contract AgentProfileResolver is AttributeStorage {
|
|
35
|
+
address[] private _agents;
|
|
36
|
+
mapping(address => bool) private _registered;
|
|
37
|
+
|
|
38
|
+
event AgentRegistered(address indexed agent, string displayName, bytes32 indexed agentKind);
|
|
39
|
+
event MetadataUpdated(address indexed agent, string metadataURI, bytes32 metadataHash);
|
|
40
|
+
event PropertySet(address indexed agent, bytes32 indexed predicate);
|
|
41
|
+
|
|
42
|
+
error NotAgentOwner();
|
|
43
|
+
error AlreadyRegistered();
|
|
44
|
+
error NotRegistered();
|
|
45
|
+
|
|
46
|
+
/// @notice `msg.sender == agent` (the canonical path). For any
|
|
47
|
+
/// other principal, callers must route through their own
|
|
48
|
+
/// CustodyPolicy / AgentAccount execute path.
|
|
49
|
+
modifier onlyAgent(address agent) {
|
|
50
|
+
if (msg.sender != agent) revert NotAgentOwner();
|
|
51
|
+
_;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
modifier onlyRegistered(address agent) {
|
|
55
|
+
if (!_registered[agent]) revert NotRegistered();
|
|
56
|
+
_;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
constructor(address ontologyRegistry) AttributeStorage(ontologyRegistry) {}
|
|
60
|
+
|
|
61
|
+
function _subject(address agent) internal pure returns (bytes32) {
|
|
62
|
+
return bytes32(uint256(uint160(agent)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Registration ───────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @notice One-time profile registration for a Smart Agent. The
|
|
69
|
+
* agent calls this on itself (`msg.sender == agent`).
|
|
70
|
+
* Subsequent edits go through the typed setters below.
|
|
71
|
+
*/
|
|
72
|
+
function register(
|
|
73
|
+
address agent,
|
|
74
|
+
string calldata displayName,
|
|
75
|
+
string calldata description,
|
|
76
|
+
bytes32 agentKind,
|
|
77
|
+
string calldata profileSchemaURI
|
|
78
|
+
) external onlyAgent(agent) {
|
|
79
|
+
if (_registered[agent]) revert AlreadyRegistered();
|
|
80
|
+
bytes32 s = _subject(agent);
|
|
81
|
+
_setString(s, AgentProfilePredicates.ATL_DISPLAY_NAME, displayName);
|
|
82
|
+
if (bytes(description).length > 0) {
|
|
83
|
+
_setString(s, AgentProfilePredicates.ATL_DESCRIPTION, description);
|
|
84
|
+
}
|
|
85
|
+
if (agentKind != bytes32(0)) {
|
|
86
|
+
_setBytes32(s, AgentProfilePredicates.ATL_AGENT_KIND, agentKind);
|
|
87
|
+
}
|
|
88
|
+
if (bytes(profileSchemaURI).length > 0) {
|
|
89
|
+
_setString(s, AgentProfilePredicates.ATL_PROFILE_SCHEMA_URI, profileSchemaURI);
|
|
90
|
+
}
|
|
91
|
+
_setBool(s, AgentProfilePredicates.ATL_PROFILE_ACTIVE, true);
|
|
92
|
+
_setUint(s, AgentProfilePredicates.ATL_PROFILE_REGISTERED_AT, block.timestamp);
|
|
93
|
+
_registered[agent] = true;
|
|
94
|
+
_agents.push(agent);
|
|
95
|
+
emit AgentRegistered(agent, displayName, agentKind);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── Typed setters ──────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
function setMetadata(
|
|
101
|
+
address agent,
|
|
102
|
+
string calldata metadataURI,
|
|
103
|
+
bytes32 metadataHash
|
|
104
|
+
) external onlyAgent(agent) onlyRegistered(agent) {
|
|
105
|
+
bytes32 s = _subject(agent);
|
|
106
|
+
_setString(s, AgentProfilePredicates.ATL_METADATA_URI, metadataURI);
|
|
107
|
+
_setBytes32(s, AgentProfilePredicates.ATL_METADATA_HASH, metadataHash);
|
|
108
|
+
emit MetadataUpdated(agent, metadataURI, metadataHash);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function setStringProperty(address agent, bytes32 predicate, string calldata value)
|
|
112
|
+
external onlyAgent(agent) onlyRegistered(agent)
|
|
113
|
+
{
|
|
114
|
+
_setString(_subject(agent), predicate, value);
|
|
115
|
+
emit PropertySet(agent, predicate);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function setAddressProperty(address agent, bytes32 predicate, address value)
|
|
119
|
+
external onlyAgent(agent) onlyRegistered(agent)
|
|
120
|
+
{
|
|
121
|
+
_setAddress(_subject(agent), predicate, value);
|
|
122
|
+
emit PropertySet(agent, predicate);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function setBoolProperty(address agent, bytes32 predicate, bool value)
|
|
126
|
+
external onlyAgent(agent) onlyRegistered(agent)
|
|
127
|
+
{
|
|
128
|
+
_setBool(_subject(agent), predicate, value);
|
|
129
|
+
emit PropertySet(agent, predicate);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function setBytes32Property(address agent, bytes32 predicate, bytes32 value)
|
|
133
|
+
external onlyAgent(agent) onlyRegistered(agent)
|
|
134
|
+
{
|
|
135
|
+
_setBytes32(_subject(agent), predicate, value);
|
|
136
|
+
emit PropertySet(agent, predicate);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function setUintProperty(address agent, bytes32 predicate, uint256 value)
|
|
140
|
+
external onlyAgent(agent) onlyRegistered(agent)
|
|
141
|
+
{
|
|
142
|
+
_setUint(_subject(agent), predicate, value);
|
|
143
|
+
emit PropertySet(agent, predicate);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function setActive(address agent, bool active)
|
|
147
|
+
external onlyAgent(agent) onlyRegistered(agent)
|
|
148
|
+
{
|
|
149
|
+
_setBool(_subject(agent), AgentProfilePredicates.ATL_PROFILE_ACTIVE, active);
|
|
150
|
+
emit PropertySet(agent, AgentProfilePredicates.ATL_PROFILE_ACTIVE);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Convenience readers ────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
function isRegistered(address agent) external view returns (bool) {
|
|
156
|
+
return _registered[agent];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getStringProperty(address agent, bytes32 predicate) external view returns (string memory) {
|
|
160
|
+
return this.getString(_subject(agent), predicate);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getAddressProperty(address agent, bytes32 predicate) external view returns (address) {
|
|
164
|
+
return this.getAddress(_subject(agent), predicate);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getBoolProperty(address agent, bytes32 predicate) external view returns (bool) {
|
|
168
|
+
return this.getBool(_subject(agent), predicate);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getBytes32Property(address agent, bytes32 predicate) external view returns (bytes32) {
|
|
172
|
+
return this.getBytes32(_subject(agent), predicate);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getUintProperty(address agent, bytes32 predicate) external view returns (uint256) {
|
|
176
|
+
return this.getUint(_subject(agent), predicate);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getPredicateKeys(address agent) external view returns (bytes32[] memory) {
|
|
180
|
+
return this.predicatesOf(_subject(agent));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function agentCount() external view returns (uint256) {
|
|
184
|
+
return _agents.length;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getAllAgents() external view returns (address[] memory) {
|
|
188
|
+
return _agents;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function subjectFor(address agent) external pure returns (bytes32) {
|
|
192
|
+
return bytes32(uint256(uint160(agent)));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @title MultiSendCallOnly
|
|
6
|
+
* @notice Atomic batched-call library, ported from Safe's
|
|
7
|
+
* `MultiSendCallOnly` shape. Lets an `AgentAccount` execute
|
|
8
|
+
* multiple inner calls in one userOp without changing
|
|
9
|
+
* `execute`'s ABI — important for flows like
|
|
10
|
+
* `approveHash + redeem` (multi-sig pre-approval path) and the
|
|
11
|
+
* coming Treasury withdraw + emit-event combinations.
|
|
12
|
+
*
|
|
13
|
+
* @dev This is the **call-only** variant. The full Safe `MultiSend`
|
|
14
|
+
* supports `op = 1` (delegatecall) but we explicitly disallow it
|
|
15
|
+
* here because delegatecall from a smart account whose authority
|
|
16
|
+
* is gated by caveats is a footgun — a single malicious target
|
|
17
|
+
* can nullify every caveat we enforce. If you need
|
|
18
|
+
* delegatecall semantics inside a quorum-gated flow, build a
|
|
19
|
+
* dedicated module + caveat rather than reaching for this
|
|
20
|
+
* library.
|
|
21
|
+
*
|
|
22
|
+
* Packed format per entry (one slot):
|
|
23
|
+
* {1 byte op}{20 bytes target}{32 bytes value}{32 bytes dataLen}{dataLen bytes data}
|
|
24
|
+
* where `op` MUST be 0 (call). Total slot size is `0x55 + dataLen`.
|
|
25
|
+
*
|
|
26
|
+
* MUST be invoked via `delegatecall` from the caller's
|
|
27
|
+
* `AgentAccount` so each inner call's `msg.sender` is the account
|
|
28
|
+
* itself (not this library). This contract is stateless — safe
|
|
29
|
+
* to deploy once per chain and reuse from any account.
|
|
30
|
+
*
|
|
31
|
+
* Reverts on the first inner failure with the failing call index
|
|
32
|
+
* and the inner revert data so callers can decode which sub-call
|
|
33
|
+
* broke.
|
|
34
|
+
*/
|
|
35
|
+
library MultiSendCallOnly {
|
|
36
|
+
error InvalidOperation(uint8 op);
|
|
37
|
+
error CallFailed(uint256 index, bytes returnData);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @notice Iterate the packed batch and invoke each call.
|
|
41
|
+
*/
|
|
42
|
+
function multiSend(bytes memory transactions) internal {
|
|
43
|
+
uint256 i;
|
|
44
|
+
uint256 n = transactions.length;
|
|
45
|
+
uint256 callIndex;
|
|
46
|
+
while (i < n) {
|
|
47
|
+
uint8 operation;
|
|
48
|
+
address to;
|
|
49
|
+
uint256 value;
|
|
50
|
+
uint256 dataLength;
|
|
51
|
+
bytes memory data;
|
|
52
|
+
|
|
53
|
+
assembly {
|
|
54
|
+
let pos := add(transactions, add(0x20, i))
|
|
55
|
+
operation := shr(248, mload(pos)) // 1 byte
|
|
56
|
+
to := shr(96, mload(add(pos, 0x01))) // 20 bytes
|
|
57
|
+
value := mload(add(pos, 0x15)) // 32 bytes
|
|
58
|
+
dataLength := mload(add(pos, 0x35)) // 32 bytes
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (operation != 0) revert InvalidOperation(operation);
|
|
62
|
+
|
|
63
|
+
// Slice the data segment into a fresh `bytes`.
|
|
64
|
+
data = new bytes(dataLength);
|
|
65
|
+
assembly {
|
|
66
|
+
let pos := add(transactions, add(0x20, i))
|
|
67
|
+
let dataStart := add(pos, 0x55)
|
|
68
|
+
let dst := add(data, 0x20)
|
|
69
|
+
for { let j := 0 } lt(j, dataLength) { j := add(j, 0x20) } {
|
|
70
|
+
mstore(add(dst, j), mload(add(dataStart, j)))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
(bool success, bytes memory ret) = to.call{ value: value }(data);
|
|
75
|
+
if (!success) revert CallFailed(callIndex, ret);
|
|
76
|
+
|
|
77
|
+
// Advance: 1 + 20 + 32 + 32 + dataLength
|
|
78
|
+
i += 0x55 + dataLength;
|
|
79
|
+
callIndex += 1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @title MultiSendCallOnlyHarness
|
|
86
|
+
* @notice Test-only wrapper that exposes `multiSend` as an external
|
|
87
|
+
* entry point so Foundry can invoke it directly. Production
|
|
88
|
+
* usage `delegatecall`s the library from an `AgentAccount`.
|
|
89
|
+
*/
|
|
90
|
+
contract MultiSendCallOnlyHarness {
|
|
91
|
+
function multiSend(bytes calldata transactions) external payable {
|
|
92
|
+
bytes memory copy = transactions;
|
|
93
|
+
MultiSendCallOnly.multiSend(copy);
|
|
94
|
+
}
|
|
95
|
+
}
|