@keelcodes/conformance 0.1.0
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/LICENSE +201 -0
- package/README.md +187 -0
- package/dist/abi.d.ts +219 -0
- package/dist/abi.d.ts.map +1 -0
- package/dist/abi.js +41 -0
- package/dist/abi.js.map +1 -0
- package/dist/agents.d.ts +24 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +161 -0
- package/dist/agents.js.map +1 -0
- package/dist/checks.d.ts +12 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +168 -0
- package/dist/checks.js.map +1 -0
- package/dist/delegation.d.ts +40 -0
- package/dist/delegation.d.ts.map +1 -0
- package/dist/delegation.js +245 -0
- package/dist/delegation.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions.d.ts +52 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +189 -0
- package/dist/permissions.js.map +1 -0
- package/dist/reader.d.ts +9 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +58 -0
- package/dist/reader.js.map +1 -0
- package/dist/redteam.d.ts +38 -0
- package/dist/redteam.d.ts.map +1 -0
- package/dist/redteam.js +190 -0
- package/dist/redteam.js.map +1 -0
- package/dist/report.d.ts +4 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +29 -0
- package/dist/report.js.map +1 -0
- package/dist/runner.d.ts +8 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +62 -0
- package/dist/runner.js.map +1 -0
- package/dist/types.d.ts +227 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAe,GAAG,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE/F;;;;;;;;GAQG;AAEH,mGAAmG;AACnG,eAAO,MAAM,mBAAmB,EAAE,GAAkB,CAAC;AAYrD,eAAO,MAAM,cAAc,EAAE,SAAS,KAAK,EAwF1C,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,sEAAsE;AACtE,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,wBAAwB,GAChC,mBAAmB,CAwDrB"}
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { erc8004IdentityAbi, erc8004ReputationAbi } from './abi.js';
|
|
2
|
+
/**
|
|
3
|
+
* ERC-8004 conformance (Trustless Agents).
|
|
4
|
+
*
|
|
5
|
+
* The identity registry is an ERC-721 whose `tokenId` is the `agentId`, with
|
|
6
|
+
* agent-specific extensions (`getAgentWallet`, `agentURI`). The suite checks the
|
|
7
|
+
* registry is a real ERC-721 and that the agent under test is registered with a
|
|
8
|
+
* resolvable owner, URI and wallet; optionally that a reputation registry is
|
|
9
|
+
* bound to the same identity registry.
|
|
10
|
+
*/
|
|
11
|
+
/** `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`-style id for ERC-721. */
|
|
12
|
+
export const ERC721_INTERFACE_ID = '0x80ac58cd';
|
|
13
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
14
|
+
function agentReader(target) {
|
|
15
|
+
const reader = target.agentRegistry;
|
|
16
|
+
if (reader === undefined) {
|
|
17
|
+
throw new Error('the ERC-8004 suite needs an `agentRegistry` reader on the target');
|
|
18
|
+
}
|
|
19
|
+
return reader;
|
|
20
|
+
}
|
|
21
|
+
export const ERC8004_CHECKS = [
|
|
22
|
+
{
|
|
23
|
+
id: 'erc8004.identity.deployed',
|
|
24
|
+
title: 'Identity registry is deployed',
|
|
25
|
+
spec: 'ERC-8004 §Identity Registry',
|
|
26
|
+
severity: 'critical',
|
|
27
|
+
async run(target) {
|
|
28
|
+
const reader = agentReader(target);
|
|
29
|
+
if (!(await reader.hasCode(reader.identityRegistry))) {
|
|
30
|
+
throw new Error(`no bytecode at identity registry ${reader.identityRegistry}`);
|
|
31
|
+
}
|
|
32
|
+
return `identity registry ${reader.identityRegistry} has bytecode`;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'erc8004.identity.erc721',
|
|
37
|
+
title: 'Identity registry implements ERC-721',
|
|
38
|
+
spec: 'ERC-8004 §Identity Registry',
|
|
39
|
+
severity: 'high',
|
|
40
|
+
async run(target) {
|
|
41
|
+
const reader = agentReader(target);
|
|
42
|
+
if (!(await reader.supportsInterface(ERC721_INTERFACE_ID))) {
|
|
43
|
+
throw new Error('supportsInterface(ERC-721) returned false');
|
|
44
|
+
}
|
|
45
|
+
return 'registry reports ERC-721 support';
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'erc8004.identity.agentRegistered',
|
|
50
|
+
title: 'Agent has a non-zero owner',
|
|
51
|
+
spec: 'ERC-8004 §register',
|
|
52
|
+
severity: 'critical',
|
|
53
|
+
async run(target) {
|
|
54
|
+
const reader = agentReader(target);
|
|
55
|
+
const owner = await reader.ownerOf();
|
|
56
|
+
if (owner.toLowerCase() === ZERO_ADDRESS) {
|
|
57
|
+
throw new Error(`agent ${reader.agentId} is not registered (ownerOf == address(0))`);
|
|
58
|
+
}
|
|
59
|
+
return `agent ${reader.agentId} owned by ${owner}`;
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'erc8004.identity.agentURI',
|
|
64
|
+
title: 'Agent exposes a resolvable URI',
|
|
65
|
+
spec: 'ERC-8004 §setAgentURI',
|
|
66
|
+
severity: 'medium',
|
|
67
|
+
async run(target) {
|
|
68
|
+
const reader = agentReader(target);
|
|
69
|
+
const uri = await reader.tokenURI();
|
|
70
|
+
if (typeof uri !== 'string' || uri.length === 0) {
|
|
71
|
+
throw new Error(`tokenURI(${reader.agentId}) is empty`);
|
|
72
|
+
}
|
|
73
|
+
return `agentURI set (${uri.length} chars)`;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'erc8004.identity.agentWallet',
|
|
78
|
+
title: 'Agent has a bound wallet',
|
|
79
|
+
spec: 'ERC-8004 §getAgentWallet',
|
|
80
|
+
severity: 'high',
|
|
81
|
+
async run(target) {
|
|
82
|
+
const reader = agentReader(target);
|
|
83
|
+
const wallet = await reader.agentWallet();
|
|
84
|
+
if (wallet.toLowerCase() === ZERO_ADDRESS) {
|
|
85
|
+
throw new Error(`getAgentWallet(${reader.agentId}) is address(0)`);
|
|
86
|
+
}
|
|
87
|
+
return `agent wallet ${wallet}`;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'erc8004.reputation.bound',
|
|
92
|
+
title: 'Reputation registry is bound to the identity registry',
|
|
93
|
+
spec: 'ERC-8004 §Reputation Registry',
|
|
94
|
+
severity: 'high',
|
|
95
|
+
async run(target) {
|
|
96
|
+
const reader = agentReader(target);
|
|
97
|
+
if (reader.reputationRegistry === undefined) {
|
|
98
|
+
throw new Error('no reputation registry configured on the reader');
|
|
99
|
+
}
|
|
100
|
+
const bound = await reader.reputationIdentityRegistry();
|
|
101
|
+
if (bound.toLowerCase() !== reader.identityRegistry.toLowerCase()) {
|
|
102
|
+
throw new Error(`reputation registry points at ${bound}, expected identity registry ${reader.identityRegistry}`);
|
|
103
|
+
}
|
|
104
|
+
return 'reputation registry bound to the identity registry';
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
/** ERC-8004 agent registry suite. */
|
|
109
|
+
export const ERC8004_SUITE = {
|
|
110
|
+
name: 'ERC-8004',
|
|
111
|
+
spec: 'ERC-8004 (Trustless Agents)',
|
|
112
|
+
checks: ERC8004_CHECKS,
|
|
113
|
+
};
|
|
114
|
+
/** Points the ERC-8004 suite at live registries via a viem client. */
|
|
115
|
+
export function createViemAgentRegistryReader(client, options) {
|
|
116
|
+
const { identityRegistry, agentId, reputationRegistry } = options;
|
|
117
|
+
return {
|
|
118
|
+
identityRegistry,
|
|
119
|
+
agentId,
|
|
120
|
+
...(reputationRegistry !== undefined ? { reputationRegistry } : {}),
|
|
121
|
+
async hasCode(address) {
|
|
122
|
+
const code = await client.getCode({ address });
|
|
123
|
+
return Boolean(code && code !== '0x');
|
|
124
|
+
},
|
|
125
|
+
ownerOf: () => client.readContract({
|
|
126
|
+
address: identityRegistry,
|
|
127
|
+
abi: erc8004IdentityAbi,
|
|
128
|
+
functionName: 'ownerOf',
|
|
129
|
+
args: [agentId],
|
|
130
|
+
}),
|
|
131
|
+
tokenURI: () => client.readContract({
|
|
132
|
+
address: identityRegistry,
|
|
133
|
+
abi: erc8004IdentityAbi,
|
|
134
|
+
functionName: 'tokenURI',
|
|
135
|
+
args: [agentId],
|
|
136
|
+
}),
|
|
137
|
+
agentWallet: () => client.readContract({
|
|
138
|
+
address: identityRegistry,
|
|
139
|
+
abi: erc8004IdentityAbi,
|
|
140
|
+
functionName: 'getAgentWallet',
|
|
141
|
+
args: [agentId],
|
|
142
|
+
}),
|
|
143
|
+
supportsInterface: (interfaceId) => client.readContract({
|
|
144
|
+
address: identityRegistry,
|
|
145
|
+
abi: erc8004IdentityAbi,
|
|
146
|
+
functionName: 'supportsInterface',
|
|
147
|
+
args: [interfaceId],
|
|
148
|
+
}),
|
|
149
|
+
reputationIdentityRegistry: () => {
|
|
150
|
+
if (reputationRegistry === undefined) {
|
|
151
|
+
return Promise.reject(new Error('no reputation registry configured'));
|
|
152
|
+
}
|
|
153
|
+
return client.readContract({
|
|
154
|
+
address: reputationRegistry,
|
|
155
|
+
abi: erc8004ReputationAbi,
|
|
156
|
+
functionName: 'getIdentityRegistry',
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
package/dist/checks.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Check, type Suite } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ERC-7579 core conformance checks.
|
|
4
|
+
*
|
|
5
|
+
* Coverage in this slice is ERC-7579 (the account surface + the module install
|
|
6
|
+
* contract). ERC-7710 / 7715 / 8004 live in `delegation.ts`, `permissions.ts`
|
|
7
|
+
* and `agents.ts` — see `docs/CONFORMANCE.md` for the coverage matrix.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ERC7579_CHECKS: readonly Check[];
|
|
10
|
+
/** ERC-7579 core suite. */
|
|
11
|
+
export declare const ERC7579_SUITE: Suite;
|
|
12
|
+
//# sourceMappingURL=checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnH;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,KAAK,EAkK1C,CAAC;AAEF,2BAA2B;AAC3B,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC"}
|
package/dist/checks.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { EXECUTION_MODE, MODULE_TYPE, accountTarget, moduleAdminTarget } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ERC-7579 core conformance checks.
|
|
4
|
+
*
|
|
5
|
+
* Coverage in this slice is ERC-7579 (the account surface + the module install
|
|
6
|
+
* contract). ERC-7710 / 7715 / 8004 live in `delegation.ts`, `permissions.ts`
|
|
7
|
+
* and `agents.ts` — see `docs/CONFORMANCE.md` for the coverage matrix.
|
|
8
|
+
*/
|
|
9
|
+
export const ERC7579_CHECKS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'erc7579.account.deployed',
|
|
12
|
+
title: 'Account is deployed',
|
|
13
|
+
spec: 'ERC-7579 §Account',
|
|
14
|
+
severity: 'critical',
|
|
15
|
+
async run(target) {
|
|
16
|
+
const { reader, account } = accountTarget(target);
|
|
17
|
+
if (!(await reader.hasCode(account))) {
|
|
18
|
+
throw new Error(`no bytecode at ${account}`);
|
|
19
|
+
}
|
|
20
|
+
return 'account has bytecode';
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'erc7579.account.supportsSingleMode',
|
|
25
|
+
title: 'Account supports the single execution mode',
|
|
26
|
+
spec: 'ERC-7579 §3.1',
|
|
27
|
+
severity: 'high',
|
|
28
|
+
async run(target) {
|
|
29
|
+
const { reader, account } = accountTarget(target);
|
|
30
|
+
if (!(await reader.supportsExecutionMode(account, EXECUTION_MODE.SINGLE))) {
|
|
31
|
+
throw new Error('supportsExecutionMode(single default) returned false');
|
|
32
|
+
}
|
|
33
|
+
return 'single (callType 0x00) supported';
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'erc7579.account.supportsBatchMode',
|
|
38
|
+
title: 'Account supports the batch execution mode',
|
|
39
|
+
spec: 'ERC-7579 §3.1',
|
|
40
|
+
severity: 'high',
|
|
41
|
+
async run(target) {
|
|
42
|
+
const { reader, account } = accountTarget(target);
|
|
43
|
+
if (!(await reader.supportsExecutionMode(account, EXECUTION_MODE.BATCH))) {
|
|
44
|
+
throw new Error('supportsExecutionMode(batch default) returned false');
|
|
45
|
+
}
|
|
46
|
+
return 'batch (callType 0x01) supported';
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'erc7579.account.accountId',
|
|
51
|
+
title: 'Account reports a non-empty accountId()',
|
|
52
|
+
spec: 'ERC-7579 §Account',
|
|
53
|
+
severity: 'medium',
|
|
54
|
+
async run(target) {
|
|
55
|
+
const { reader, account } = accountTarget(target);
|
|
56
|
+
const id = await reader.accountId(account);
|
|
57
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
58
|
+
throw new Error(`accountId() returned ${JSON.stringify(id)}`);
|
|
59
|
+
}
|
|
60
|
+
return `accountId() = ${id}`;
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'erc7579.module.installed',
|
|
65
|
+
title: 'Module is reported installed by the account',
|
|
66
|
+
spec: 'ERC-7579 §ModuleManager',
|
|
67
|
+
severity: 'critical',
|
|
68
|
+
async run(target) {
|
|
69
|
+
const { reader, account, module, moduleTypeId } = accountTarget(target);
|
|
70
|
+
const installed = await reader.isModuleInstalled({ account, module, moduleTypeId });
|
|
71
|
+
if (!installed) {
|
|
72
|
+
throw new Error(`isModuleInstalled(${moduleTypeId}, ${module}) returned false — the module is not installed`);
|
|
73
|
+
}
|
|
74
|
+
return `module ${module} installed as type ${moduleTypeId}`;
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'erc7579.module.initialized',
|
|
79
|
+
title: 'Installed module reports isInitialized(account)',
|
|
80
|
+
spec: 'ERC-7579 §IModule',
|
|
81
|
+
severity: 'critical',
|
|
82
|
+
async run(target) {
|
|
83
|
+
const { reader, account, module } = accountTarget(target);
|
|
84
|
+
// The second half of the migration probe: a module can be *registered* on
|
|
85
|
+
// the account while its own `onInstall` silently no-op'd (a layout or
|
|
86
|
+
// value mismatch that does not revert). The account then looks configured
|
|
87
|
+
// but the module enforces nothing — an AA24-class silent failure.
|
|
88
|
+
if (!(await reader.isModuleInitialized(module, account))) {
|
|
89
|
+
throw new Error('module is installed but reports isInitialized == false — silent no-op install (AA24-class)');
|
|
90
|
+
}
|
|
91
|
+
return 'module reports initialized';
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'erc7579.module.installUninstallFlow',
|
|
96
|
+
title: 'Module can be installed and uninstalled by type',
|
|
97
|
+
spec: 'ERC-7579 §ModuleManager',
|
|
98
|
+
severity: 'critical',
|
|
99
|
+
async run(target) {
|
|
100
|
+
const { reader, moduleAdmin, account, module, moduleTypeId } = moduleAdminTarget(target);
|
|
101
|
+
// A round trip, so start from a clean account: a pre-existing install
|
|
102
|
+
// would make `installModule` revert or could mask a broken uninstall.
|
|
103
|
+
if (await reader.isModuleInstalled({ account, module, moduleTypeId })) {
|
|
104
|
+
await moduleAdmin.uninstallModule({ account, module, moduleTypeId });
|
|
105
|
+
}
|
|
106
|
+
await moduleAdmin.installModule({ account, module, moduleTypeId });
|
|
107
|
+
if (!(await reader.isModuleInstalled({ account, module, moduleTypeId }))) {
|
|
108
|
+
throw new Error(`installModule(${moduleTypeId}, ${module}) did not register the module on ${account}`);
|
|
109
|
+
}
|
|
110
|
+
await moduleAdmin.uninstallModule({ account, module, moduleTypeId });
|
|
111
|
+
if (await reader.isModuleInstalled({ account, module, moduleTypeId })) {
|
|
112
|
+
throw new Error(`uninstallModule(${moduleTypeId}, ${module}) left the module installed on ${account}`);
|
|
113
|
+
}
|
|
114
|
+
return `module ${module} installed and uninstalled as type ${moduleTypeId}`;
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'erc7579.module.onInstallOnUninstall',
|
|
119
|
+
title: 'onInstall / onUninstall take effect on the module',
|
|
120
|
+
spec: 'ERC-7579 §IModule',
|
|
121
|
+
severity: 'high',
|
|
122
|
+
async run(target) {
|
|
123
|
+
const { reader, moduleAdmin, account, module, moduleTypeId } = moduleAdminTarget(target);
|
|
124
|
+
// The lifecycle callbacks are only observable through their effect on the
|
|
125
|
+
// module's own state: initialized after install, cleared after uninstall.
|
|
126
|
+
// A silent no-op on either side is exactly the failure this catches.
|
|
127
|
+
if (await reader.isModuleInstalled({ account, module, moduleTypeId })) {
|
|
128
|
+
await moduleAdmin.uninstallModule({ account, module, moduleTypeId });
|
|
129
|
+
}
|
|
130
|
+
await moduleAdmin.installModule({ account, module, moduleTypeId });
|
|
131
|
+
if (!(await reader.isModuleInitialized(module, account))) {
|
|
132
|
+
throw new Error('module reports isInitialized == false after install — onInstall silently no-op\'d');
|
|
133
|
+
}
|
|
134
|
+
await moduleAdmin.uninstallModule({ account, module, moduleTypeId });
|
|
135
|
+
if (await reader.isModuleInitialized(module, account)) {
|
|
136
|
+
throw new Error('module reports isInitialized == true after uninstall — onUninstall silently no-op\'d');
|
|
137
|
+
}
|
|
138
|
+
return 'isInitialized tracks install and uninstall';
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'erc7579.module.isModuleType',
|
|
143
|
+
title: 'Module reports true for exactly its own ERC-7579 type',
|
|
144
|
+
spec: 'ERC-7579 §IModule',
|
|
145
|
+
severity: 'high',
|
|
146
|
+
async run(target) {
|
|
147
|
+
const { reader, module, moduleTypeId } = accountTarget(target);
|
|
148
|
+
// Exercises all four MODULE_TYPE ids: the module must claim its own type
|
|
149
|
+
// and disclaim the other three — a module that answers true to everything
|
|
150
|
+
// (or to the wrong type) would be dispatched as the wrong kind.
|
|
151
|
+
for (const [name, typeId] of Object.entries(MODULE_TYPE)) {
|
|
152
|
+
const expected = typeId === moduleTypeId;
|
|
153
|
+
const actual = await reader.isModuleType(module, typeId);
|
|
154
|
+
if (actual !== expected) {
|
|
155
|
+
throw new Error(`isModuleType(${name}=${typeId}) = ${actual}, expected ${expected} for a type-${moduleTypeId} module`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return `module ${module} claims only its type ${moduleTypeId}`;
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
/** ERC-7579 core suite. */
|
|
163
|
+
export const ERC7579_SUITE = {
|
|
164
|
+
name: 'ERC-7579',
|
|
165
|
+
spec: 'ERC-7579 (Modular Smart Accounts)',
|
|
166
|
+
checks: ERC7579_CHECKS,
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type PublicClient } from 'viem';
|
|
2
|
+
import type { Address, Check, Delegation, DelegationCaveat, DelegationReader, Hex, Suite } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* ERC-7710 conformance (Smart Contract Delegation).
|
|
5
|
+
*
|
|
6
|
+
* The EIP body fixes only `redeemDelegations(bytes[],bytes32[],bytes[])`; the
|
|
7
|
+
* getters probed here are the MetaMask delegation-framework reference that the
|
|
8
|
+
* EIP names. The suite's core assertion is **off-chain / on-chain agreement**:
|
|
9
|
+
* the delegation hash we derive from the EIP-712 preimage must equal what the
|
|
10
|
+
* manager returns, and the domain separator must be bound to this chain and
|
|
11
|
+
* manager — a mismatch is a replay vector across chains or deployments.
|
|
12
|
+
*/
|
|
13
|
+
/** `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. */
|
|
14
|
+
export declare const EIP712_DOMAIN_TYPEHASH: `0x${string}`;
|
|
15
|
+
/** `ROOT_AUTHORITY` — the sentinel authority for a root delegation. */
|
|
16
|
+
export declare const ROOT_AUTHORITY: Hex;
|
|
17
|
+
/** `ANY_DELEGATE` — the reference's wildcard delegate sentinel. */
|
|
18
|
+
export declare const ANY_DELEGATE: Address;
|
|
19
|
+
/**
|
|
20
|
+
* `keccak256(abi.encode(CAVEAT_TYPEHASH, enforcer, keccak256(terms)))` — the
|
|
21
|
+
* caveat's `args` are deliberately excluded, matching the reference.
|
|
22
|
+
*/
|
|
23
|
+
export declare function caveatHash(caveat: DelegationCaveat): Hex;
|
|
24
|
+
/** The EIP-712 delegation hash, derived off-chain. */
|
|
25
|
+
export declare function computeDelegationHash(delegation: Delegation): Hex;
|
|
26
|
+
/** The EIP-712 domain separator for a manager on a chain. */
|
|
27
|
+
export declare function computeDomainHash(args: {
|
|
28
|
+
manager: Address;
|
|
29
|
+
chainId: number;
|
|
30
|
+
name?: string;
|
|
31
|
+
version?: string;
|
|
32
|
+
}): Hex;
|
|
33
|
+
/** A delegation fixture used to exercise the hash derivation. */
|
|
34
|
+
export declare const SAMPLE_DELEGATION: Delegation;
|
|
35
|
+
export declare const ERC7710_CHECKS: readonly Check[];
|
|
36
|
+
/** ERC-7710 delegation suite. */
|
|
37
|
+
export declare const ERC7710_SUITE: Suite;
|
|
38
|
+
/** Points the ERC-7710 suite at a live `DelegationManager` via a viem client. */
|
|
39
|
+
export declare function createViemDelegationReader(client: PublicClient, manager: Address): DelegationReader;
|
|
40
|
+
//# sourceMappingURL=delegation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../src/delegation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuD,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AAE9F,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAe,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE1H;;;;;;;;;GASG;AAEH,yGAAyG;AACzG,eAAO,MAAM,sBAAsB,eAElC,CAAC;AAUF,uEAAuE;AACvE,eAAO,MAAM,cAAc,EAAE,GAA4B,CAAC;AAC1D,mEAAmE;AACnE,eAAO,MAAM,YAAY,EAAE,OAAsD,CAAC;AAKlF;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,GAAG,CAOxD;AASD,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,GAAG,CAqBjE;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,GAAG,CAmBN;AAED,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,EAAE,UAM/B,CAAC;AAcF,eAAO,MAAM,cAAc,EAAE,SAAS,KAAK,EA6G1C,CAAC;AAEF,iCAAiC;AACjC,eAAO,MAAM,aAAa,EAAE,KAI3B,CAAC;AAiBF,iFAAiF;AACjF,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAyCnG"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { encodeAbiParameters, encodePacked, keccak256, toHex } from 'viem';
|
|
2
|
+
import { erc7710ManagerAbi } from './abi.js';
|
|
3
|
+
/**
|
|
4
|
+
* ERC-7710 conformance (Smart Contract Delegation).
|
|
5
|
+
*
|
|
6
|
+
* The EIP body fixes only `redeemDelegations(bytes[],bytes32[],bytes[])`; the
|
|
7
|
+
* getters probed here are the MetaMask delegation-framework reference that the
|
|
8
|
+
* EIP names. The suite's core assertion is **off-chain / on-chain agreement**:
|
|
9
|
+
* the delegation hash we derive from the EIP-712 preimage must equal what the
|
|
10
|
+
* manager returns, and the domain separator must be bound to this chain and
|
|
11
|
+
* manager — a mismatch is a replay vector across chains or deployments.
|
|
12
|
+
*/
|
|
13
|
+
/** `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. */
|
|
14
|
+
export const EIP712_DOMAIN_TYPEHASH = keccak256(toHex('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'));
|
|
15
|
+
const CAVEAT_TYPEHASH = keccak256(toHex('Caveat(address enforcer,bytes terms)'));
|
|
16
|
+
const DELEGATION_TYPEHASH = keccak256(toHex('Delegation(address delegate,address delegator,bytes32 authority,Caveat[] caveats,uint256 salt)Caveat(address enforcer,bytes terms)'));
|
|
17
|
+
/** `ROOT_AUTHORITY` — the sentinel authority for a root delegation. */
|
|
18
|
+
export const ROOT_AUTHORITY = `0x${'ff'.repeat(32)}`;
|
|
19
|
+
/** `ANY_DELEGATE` — the reference's wildcard delegate sentinel. */
|
|
20
|
+
export const ANY_DELEGATE = '0x0000000000000000000000000000000000000a11';
|
|
21
|
+
const DEFAULT_DOMAIN_NAME = 'DelegationManager';
|
|
22
|
+
const DEFAULT_DOMAIN_VERSION = '1';
|
|
23
|
+
/**
|
|
24
|
+
* `keccak256(abi.encode(CAVEAT_TYPEHASH, enforcer, keccak256(terms)))` — the
|
|
25
|
+
* caveat's `args` are deliberately excluded, matching the reference.
|
|
26
|
+
*/
|
|
27
|
+
export function caveatHash(caveat) {
|
|
28
|
+
return keccak256(encodeAbiParameters([{ type: 'bytes32' }, { type: 'address' }, { type: 'bytes32' }], [CAVEAT_TYPEHASH, caveat.enforcer, keccak256(caveat.terms)]));
|
|
29
|
+
}
|
|
30
|
+
function caveatArrayHash(caveats) {
|
|
31
|
+
if (caveats.length === 0)
|
|
32
|
+
return keccak256('0x');
|
|
33
|
+
const types = caveats.map(() => 'bytes32');
|
|
34
|
+
const values = caveats.map((caveat) => caveatHash(caveat));
|
|
35
|
+
return keccak256(encodePacked(types, values));
|
|
36
|
+
}
|
|
37
|
+
/** The EIP-712 delegation hash, derived off-chain. */
|
|
38
|
+
export function computeDelegationHash(delegation) {
|
|
39
|
+
return keccak256(encodeAbiParameters([
|
|
40
|
+
{ type: 'bytes32' },
|
|
41
|
+
{ type: 'address' },
|
|
42
|
+
{ type: 'address' },
|
|
43
|
+
{ type: 'bytes32' },
|
|
44
|
+
{ type: 'bytes32' },
|
|
45
|
+
{ type: 'uint256' },
|
|
46
|
+
], [
|
|
47
|
+
DELEGATION_TYPEHASH,
|
|
48
|
+
delegation.delegate,
|
|
49
|
+
delegation.delegator,
|
|
50
|
+
delegation.authority,
|
|
51
|
+
caveatArrayHash(delegation.caveats),
|
|
52
|
+
delegation.salt,
|
|
53
|
+
]));
|
|
54
|
+
}
|
|
55
|
+
/** The EIP-712 domain separator for a manager on a chain. */
|
|
56
|
+
export function computeDomainHash(args) {
|
|
57
|
+
return keccak256(encodeAbiParameters([
|
|
58
|
+
{ type: 'bytes32' },
|
|
59
|
+
{ type: 'bytes32' },
|
|
60
|
+
{ type: 'bytes32' },
|
|
61
|
+
{ type: 'uint256' },
|
|
62
|
+
{ type: 'address' },
|
|
63
|
+
], [
|
|
64
|
+
EIP712_DOMAIN_TYPEHASH,
|
|
65
|
+
keccak256(toHex(args.name ?? DEFAULT_DOMAIN_NAME)),
|
|
66
|
+
keccak256(toHex(args.version ?? DEFAULT_DOMAIN_VERSION)),
|
|
67
|
+
BigInt(args.chainId),
|
|
68
|
+
args.manager,
|
|
69
|
+
]));
|
|
70
|
+
}
|
|
71
|
+
/** A delegation fixture used to exercise the hash derivation. */
|
|
72
|
+
export const SAMPLE_DELEGATION = {
|
|
73
|
+
delegate: ANY_DELEGATE,
|
|
74
|
+
delegator: '0x1111111111111111111111111111111111111111',
|
|
75
|
+
authority: ROOT_AUTHORITY,
|
|
76
|
+
caveats: [{ enforcer: '0x2222222222222222222222222222222222222222', terms: '0xdeadbeef' }],
|
|
77
|
+
salt: 0n,
|
|
78
|
+
};
|
|
79
|
+
function delegationReader(target) {
|
|
80
|
+
const reader = target.delegation;
|
|
81
|
+
if (reader === undefined) {
|
|
82
|
+
throw new Error('the ERC-7710 suite needs a `delegation` reader on the target');
|
|
83
|
+
}
|
|
84
|
+
return reader;
|
|
85
|
+
}
|
|
86
|
+
function toHex32(value) {
|
|
87
|
+
return value.toLowerCase();
|
|
88
|
+
}
|
|
89
|
+
export const ERC7710_CHECKS = [
|
|
90
|
+
{
|
|
91
|
+
id: 'erc7710.manager.deployed',
|
|
92
|
+
title: 'DelegationManager is deployed',
|
|
93
|
+
spec: 'ERC-7710 §ERC7710Manager',
|
|
94
|
+
severity: 'critical',
|
|
95
|
+
async run(target) {
|
|
96
|
+
const reader = delegationReader(target);
|
|
97
|
+
if (!(await reader.hasCode(reader.manager))) {
|
|
98
|
+
throw new Error(`no bytecode at DelegationManager ${reader.manager}`);
|
|
99
|
+
}
|
|
100
|
+
return `DelegationManager ${reader.manager} has bytecode`;
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'erc7710.manager.domainBinding',
|
|
105
|
+
title: 'Domain separator is bound to this chain and manager',
|
|
106
|
+
spec: 'ERC-7710 + EIP-712',
|
|
107
|
+
severity: 'high',
|
|
108
|
+
async run(target) {
|
|
109
|
+
const reader = delegationReader(target);
|
|
110
|
+
const expected = computeDomainHash({
|
|
111
|
+
manager: reader.manager,
|
|
112
|
+
chainId: await reader.chainId(),
|
|
113
|
+
name: await reader.name(),
|
|
114
|
+
version: await reader.version(),
|
|
115
|
+
});
|
|
116
|
+
const actual = await reader.domainHash();
|
|
117
|
+
if (toHex32(expected) !== toHex32(actual)) {
|
|
118
|
+
throw new Error(`getDomainHash() = ${actual}, expected ${expected} — a cross-chain/cross-contract replay vector`);
|
|
119
|
+
}
|
|
120
|
+
return 'domain separator matches the EIP-712 preimage';
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'erc7710.manager.delegationHash',
|
|
125
|
+
title: 'On-chain delegation hash matches the off-chain derivation',
|
|
126
|
+
spec: 'ERC-7710 + MetaMask delegation-framework',
|
|
127
|
+
severity: 'critical',
|
|
128
|
+
async run(target) {
|
|
129
|
+
const reader = delegationReader(target);
|
|
130
|
+
const expected = computeDelegationHash(SAMPLE_DELEGATION);
|
|
131
|
+
const actual = await reader.delegationHash(SAMPLE_DELEGATION);
|
|
132
|
+
if (toHex32(expected) !== toHex32(actual)) {
|
|
133
|
+
throw new Error(`getDelegationHash() = ${actual}, off-chain derivation = ${expected}`);
|
|
134
|
+
}
|
|
135
|
+
return 'delegation hash derivation agrees with the manager';
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'erc7710.manager.signatureIgnored',
|
|
140
|
+
title: 'Delegation hash ignores the signature field',
|
|
141
|
+
spec: 'ERC-7710 + MetaMask delegation-framework',
|
|
142
|
+
severity: 'high',
|
|
143
|
+
async run(target) {
|
|
144
|
+
const reader = delegationReader(target);
|
|
145
|
+
const withSignature = { ...SAMPLE_DELEGATION, signature: '0xdeadbeef' };
|
|
146
|
+
const plain = await reader.delegationHash(SAMPLE_DELEGATION);
|
|
147
|
+
const signed = await reader.delegationHash(withSignature);
|
|
148
|
+
if (toHex32(plain) !== toHex32(signed)) {
|
|
149
|
+
throw new Error('a signature change altered the delegation hash — signing would invalidate it');
|
|
150
|
+
}
|
|
151
|
+
return 'signature is excluded from the hash';
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'erc7710.manager.rootAuthority',
|
|
156
|
+
title: 'ROOT_AUTHORITY matches the reference sentinel',
|
|
157
|
+
spec: 'ERC-7710 + MetaMask delegation-framework',
|
|
158
|
+
severity: 'medium',
|
|
159
|
+
async run(target) {
|
|
160
|
+
const reader = delegationReader(target);
|
|
161
|
+
const actual = await reader.rootAuthority();
|
|
162
|
+
if (toHex32(actual) !== toHex32(ROOT_AUTHORITY)) {
|
|
163
|
+
throw new Error(`ROOT_AUTHORITY() = ${actual}, expected ${ROOT_AUTHORITY}`);
|
|
164
|
+
}
|
|
165
|
+
return 'ROOT_AUTHORITY is the all-ones sentinel';
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'erc7710.manager.anyDelegate',
|
|
170
|
+
title: 'ANY_DELEGATE matches the reference sentinel',
|
|
171
|
+
spec: 'ERC-7710 + MetaMask delegation-framework',
|
|
172
|
+
severity: 'medium',
|
|
173
|
+
async run(target) {
|
|
174
|
+
const reader = delegationReader(target);
|
|
175
|
+
const actual = await reader.anyDelegate();
|
|
176
|
+
if (actual.toLowerCase() !== ANY_DELEGATE.toLowerCase()) {
|
|
177
|
+
throw new Error(`ANY_DELEGATE() = ${actual}, expected ${ANY_DELEGATE}`);
|
|
178
|
+
}
|
|
179
|
+
return 'ANY_DELEGATE is the reference wildcard';
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'erc7710.manager.notDisabledByDefault',
|
|
184
|
+
title: 'An unspent delegation hash is not disabled',
|
|
185
|
+
spec: 'ERC-7710 + MetaMask delegation-framework',
|
|
186
|
+
severity: 'high',
|
|
187
|
+
async run(target) {
|
|
188
|
+
const reader = delegationReader(target);
|
|
189
|
+
const hash = computeDelegationHash({ ...SAMPLE_DELEGATION, salt: 999n });
|
|
190
|
+
if (await reader.isDelegationDisabled(hash)) {
|
|
191
|
+
throw new Error(`disabledDelegations(${hash}) = true for a fresh delegation`);
|
|
192
|
+
}
|
|
193
|
+
return 'fresh delegation hashes read as enabled';
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
/** ERC-7710 delegation suite. */
|
|
198
|
+
export const ERC7710_SUITE = {
|
|
199
|
+
name: 'ERC-7710',
|
|
200
|
+
spec: 'ERC-7710 (Smart Contract Delegation)',
|
|
201
|
+
checks: ERC7710_CHECKS,
|
|
202
|
+
};
|
|
203
|
+
function toAbiDelegation(delegation) {
|
|
204
|
+
return {
|
|
205
|
+
delegate: delegation.delegate,
|
|
206
|
+
delegator: delegation.delegator,
|
|
207
|
+
authority: delegation.authority,
|
|
208
|
+
caveats: delegation.caveats.map((caveat) => ({
|
|
209
|
+
enforcer: caveat.enforcer,
|
|
210
|
+
terms: caveat.terms,
|
|
211
|
+
args: caveat.args ?? '0x',
|
|
212
|
+
})),
|
|
213
|
+
salt: delegation.salt,
|
|
214
|
+
signature: delegation.signature ?? '0x',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Points the ERC-7710 suite at a live `DelegationManager` via a viem client. */
|
|
218
|
+
export function createViemDelegationReader(client, manager) {
|
|
219
|
+
return {
|
|
220
|
+
manager,
|
|
221
|
+
async hasCode(address) {
|
|
222
|
+
const code = await client.getCode({ address });
|
|
223
|
+
return Boolean(code && code !== '0x');
|
|
224
|
+
},
|
|
225
|
+
chainId: () => client.getChainId(),
|
|
226
|
+
domainHash: () => client.readContract({ address: manager, abi: erc7710ManagerAbi, functionName: 'getDomainHash' }),
|
|
227
|
+
delegationHash: (delegation) => client.readContract({
|
|
228
|
+
address: manager,
|
|
229
|
+
abi: erc7710ManagerAbi,
|
|
230
|
+
functionName: 'getDelegationHash',
|
|
231
|
+
args: [toAbiDelegation(delegation)],
|
|
232
|
+
}),
|
|
233
|
+
isDelegationDisabled: (delegationHash) => client.readContract({
|
|
234
|
+
address: manager,
|
|
235
|
+
abi: erc7710ManagerAbi,
|
|
236
|
+
functionName: 'disabledDelegations',
|
|
237
|
+
args: [delegationHash],
|
|
238
|
+
}),
|
|
239
|
+
name: () => client.readContract({ address: manager, abi: erc7710ManagerAbi, functionName: 'NAME' }),
|
|
240
|
+
version: () => client.readContract({ address: manager, abi: erc7710ManagerAbi, functionName: 'VERSION' }),
|
|
241
|
+
rootAuthority: () => client.readContract({ address: manager, abi: erc7710ManagerAbi, functionName: 'ROOT_AUTHORITY' }),
|
|
242
|
+
anyDelegate: () => client.readContract({ address: manager, abi: erc7710ManagerAbi, functionName: 'ANY_DELEGATE' }),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=delegation.js.map
|