Skip to content

Commit 06a5bb7

Browse files
committed
up
1 parent f44e96d commit 06a5bb7

File tree

4 files changed

+153
-28
lines changed

4 files changed

+153
-28
lines changed

packages/core/solidity/src/erc7579.ts

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,77 @@ export function buildERC7579(opts: ERC7579Options): Contract {
5656

5757
const c = new ContractBuilder(allOpts.name);
5858

59+
// Base parent
60+
c.addOverride(
61+
{
62+
name: 'IERC7579Module',
63+
},
64+
functions.isModuleType,
65+
);
66+
67+
overrideIsModuleType(c, allOpts);
5968
addParents(c, allOpts);
60-
addMultisig(c, allOpts);
69+
overrideValidation(c, allOpts);
70+
// addAccess(c, allOpts); TODO
71+
// addOnInstall(c, allOpts); TODO
6172

6273
return c;
6374
}
6475

76+
type IsModuleTypeImplementation = 'ERC7579Executor' | 'ERC7579Validator' | 'IERC7579Hook' | 'Fallback';
77+
78+
function overrideIsModuleType(c: ContractBuilder, opts: ERC7579Options): void {
79+
const implementedIn: IsModuleTypeImplementation[] = ['ERC7579Executor', 'ERC7579Validator'] as const;
80+
const types: IsModuleTypeImplementation[] = [];
81+
const fn = functions.isModuleType;
82+
83+
if (opts.executor) {
84+
types.push('ERC7579Executor');
85+
c.addOverride({ name: 'ERC7579Executor' }, fn);
86+
}
87+
88+
if (opts.validator) {
89+
types.push('ERC7579Validator');
90+
c.addOverride({ name: 'ERC7579Validator' }, fn);
91+
}
92+
93+
if (opts.hook) {
94+
types.push('IERC7579Hook');
95+
c.addOverride({ name: 'IERC7579Hook' }, fn);
96+
}
97+
98+
if (opts.fallback) {
99+
types.push('Fallback');
100+
}
101+
102+
const implementedOverrides = types.filter(type => implementedIn.includes(type));
103+
const unimplementedOverrides = types.filter(type => !implementedIn.includes(type));
104+
105+
if (implementedOverrides.length === 0 && unimplementedOverrides.length === 1) {
106+
const importedType =
107+
unimplementedOverrides[0]! === 'IERC7579Hook' ? 'MODULE_TYPE_VALIDATOR' : 'MODULE_TYPE_FALLBACK';
108+
c.setFunctionBody([`return ${fn.args[0]!.name} == ${importedType};`], fn);
109+
} else if (
110+
implementedOverrides.length >= 2 || // 1 = n/a, 2 = defaults to super
111+
unimplementedOverrides.length > 0 // Require manual comparison
112+
) {
113+
const body: string[] = [];
114+
for (const type of implementedOverrides) {
115+
body.push(`bool is${type} = ${type}.isModuleType(${fn.args[0]!.name})`);
116+
}
117+
for (const type of unimplementedOverrides) {
118+
const importedType = type === 'IERC7579Hook' ? 'MODULE_TYPE_VALIDATOR' : 'MODULE_TYPE_FALLBACK';
119+
c.addImportOnly({
120+
name: importedType,
121+
path: '@openzeppelin/contracts/interfaces/draft-IERC7579.sol',
122+
});
123+
body.push(`bool is${type} = ${fn.args[0]!.name} == ${importedType};`);
124+
}
125+
body.push(`return ${types.map(type => `is${type}`).join(' || ')};`);
126+
c.setFunctionBody(body, fn);
127+
}
128+
}
129+
65130
function addParents(c: ContractBuilder, opts: ERC7579Options): void {
66131
c.addParent({
67132
name: 'IERC7579Module',
@@ -88,6 +153,13 @@ function addParents(c: ContractBuilder, opts: ERC7579Options): void {
88153
path: '@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol',
89154
});
90155

156+
if (opts.validator.signature) {
157+
c.addParent({
158+
name: 'ERC7579Signature',
159+
path: '@openzeppelin/community-contracts/account/modules/ERC7579Signature.sol',
160+
});
161+
}
162+
91163
if (opts.validator.multisig) {
92164
c.addParent({
93165
name: 'ERC7579Multisig',
@@ -118,21 +190,31 @@ function addParents(c: ContractBuilder, opts: ERC7579Options): void {
118190
}
119191

120192
if (opts.fallback) {
121-
// NO OP
193+
// noop
122194
}
123195
}
124196

125-
function addMultisig(c: ContractBuilder, opts: ERC7579Options): void {
197+
function overrideValidation(c: ContractBuilder, opts: ERC7579Options): void {
126198
if (opts.executor) {
127-
const fn = functions._validateExecution;
199+
const delayed = !opts.executor.delayed; // Delayed ensures single execution per operation.
200+
const fn = delayed ? functions._validateSchedule : functions._validateExecution;
128201
c.addOverride(c, fn);
129202
if (opts.validator) {
130-
// _rawERC7579Validation available
203+
c.addParent(
204+
{
205+
name: 'EIP712',
206+
path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol',
207+
},
208+
[opts.name, '1'],
209+
);
210+
c.addVariable(
211+
`bytes32 public constant EXECUTION_TYPEHASH = "Execute(address account,bytes32 salt,${delayed ? 'uint256 nonce,' : ''}bytes32 mode,bytes executionCalldata)"`,
212+
);
131213
c.setFunctionBody(
132214
[
133215
`uint16 executionCalldataLength = uint16(uint256(bytes32(${fn.args[3]!.name}[0:2]))); // First 2 bytes are the length`,
134216
`bytes calldata executionCalldata = ${fn.args[3]!.name}[2:2 + executionCalldataLength]; // Next bytes are the calldata`,
135-
`bytes32 typeHash = _getExecuteTypeHash(${fn.args[0]!.name}, salt, mode, executionCalldata);`,
217+
`bytes32 typeHash = _hashTypedDataV4(keccak256(abi.encode(EXECUTION_TYPEHASH, ${fn.args[0]!.name}, ${fn.args[1]!.name},${delayed ? ` _useNonce(${fn.args[0]!.name}),` : ''} ${fn.args[2]!.name}, executionCalldata)));`,
136218
`require(_rawERC7579Validation(${fn.args[0]!.name}, typeHash, ${fn.args[3]!.name}[2 + executionCalldataLength:])); // Remaining bytes are the signature`,
137219
`return executionCalldata;`,
138220
],
@@ -154,13 +236,30 @@ const functions = {
154236
...defineFunctions({
155237
_validateExecution: {
156238
kind: 'internal' as const,
239+
mutability: 'view',
157240
args: [
158241
{ name: 'account', type: 'address' },
159-
{ name: 'hash', type: 'bytes32' },
242+
{ name: 'salt', type: 'bytes32' },
160243
{ name: 'mode', type: 'bytes32' },
161244
{ name: 'data', type: 'bytes calldata' },
162245
],
163246
returns: ['bytes calldata'],
164247
},
248+
_validateSchedule: {
249+
kind: 'internal' as const,
250+
mutability: 'view',
251+
args: [
252+
{ name: 'account', type: 'address' },
253+
{ name: 'salt', type: 'bytes32' },
254+
{ name: 'mode', type: 'bytes32' },
255+
{ name: 'data', type: 'bytes calldata' },
256+
],
257+
},
258+
isModuleType: {
259+
kind: 'public' as const,
260+
mutability: 'pure',
261+
args: [{ name: 'moduleTypeId', type: 'uint256' }],
262+
returns: ['bool'],
263+
},
165264
}),
166265
};

packages/core/solidity/src/generate/erc7579.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,22 @@ import { generateAlternatives } from './alternatives';
55

66
const erc7579 = {
77
name: ['MyERC7579'],
8-
type: [false, 'Validator', 'Executor', 'DelayedExecutor', 'FallbackHandler', 'Hook'] as const,
9-
multisig: [false, 'ERC7579Multisig', 'ERC7579MultisigConfirmation', 'ERC7579MultisigWeighted'] as const,
8+
validator: [
9+
{
10+
signature: false,
11+
multisig: {
12+
weighted: false,
13+
confirmation: false,
14+
},
15+
},
16+
] as const,
17+
executor: [
18+
{
19+
delayed: false,
20+
},
21+
] as const,
22+
hook: [false] as const,
23+
fallback: [false] as const,
1024
access: accessOptions,
1125
upgradeable: [false] as const,
1226
info: infoOptions,

packages/ui/src/solidity/ERC7579Controls.svelte

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import { erc7579 } from '@openzeppelin/wizard';
55
import InfoSection from './InfoSection.svelte';
66
import AccessControlSection from './AccessControlSection.svelte';
7-
import type { ERC7579ValidatorType } from '@openzeppelin/wizard/src/erc7579';
87
98
export let opts: Required<KindedOptions['ERC7579']> = {
109
kind: 'ERC7579',
@@ -48,21 +47,35 @@
4847
Handle signature verification and user operation validation.
4948
</HelpTooltip>
5049
</label>
50+
<label class:checked={opts.validator?.signature} class="subcontrol">
51+
<input
52+
type="checkbox"
53+
checked={opts.validator?.signature}
54+
on:change={e => {
55+
if (e.currentTarget?.checked) {
56+
opts.validator ??= {};
57+
opts.validator.signature = {};
58+
opts.validator.multisig = undefined;
59+
} else opts.validator.signature = undefined;
60+
}}
61+
/>
62+
Signature
63+
<HelpTooltip link="#">TODO</HelpTooltip>
64+
</label>
5165
<label class:checked={opts.validator?.multisig} class="subcontrol">
5266
<input
5367
type="checkbox"
5468
checked={opts.validator?.multisig}
5569
on:change={e => {
5670
if (e.currentTarget?.checked) {
5771
opts.validator ??= {};
58-
opts.validator.multisig = true;
72+
opts.validator.multisig = {};
73+
opts.validator.signature = undefined;
5974
} else opts.validator.multisig = undefined;
6075
}}
6176
/>
6277
Multisig
63-
<HelpTooltip link="https://docs.openzeppelin.com/community-contracts/0.0.1/api/account#ERC7579Executor">
64-
Execute operations on behalf of the account.
65-
</HelpTooltip>
78+
<HelpTooltip link="#">TODO</HelpTooltip>
6679
</label>
6780
<div class="checkbox-group">
6881
<label class:checked={opts.validator?.multisig?.weighted} class="subcontrol">
@@ -81,9 +94,7 @@
8194
}}
8295
/>
8396
Weighted
84-
<HelpTooltip link="https://docs.openzeppelin.com/community-contracts/0.0.1/api/account#ERC7579DelayedExecutor">
85-
Schedules operations for the account and adds a delay before executing an account operation.
86-
</HelpTooltip>
97+
<HelpTooltip link="#">TODO</HelpTooltip>
8798
</label>
8899
<label class:checked={opts.validator?.multisig?.weighted} class="subcontrol">
89100
<input
@@ -101,31 +112,32 @@
101112
}}
102113
/>
103114
Confirmation
104-
<HelpTooltip link="https://docs.openzeppelin.com/community-contracts/0.0.1/api/account#ERC7579DelayedExecutor">
105-
Schedules operations for the account and adds a delay before executing an account operation.
106-
</HelpTooltip>
115+
<HelpTooltip link="#">TODO</HelpTooltip>
107116
</label>
108117
</div>
109118
<label class:checked={opts.executor}>
110119
<input
111120
type="checkbox"
121+
checked={opts.executor}
112122
on:change={e => {
113-
opts.executor = e.currentTarget?.checked;
123+
if (e.currentTarget?.checked) opts.executor ??= {};
124+
else opts.executor = undefined;
114125
}}
115-
checked={opts.executor}
116126
/>
117127
Executor
118128
<HelpTooltip link="https://docs.openzeppelin.com/community-contracts/0.0.1/api/account#ERC7579Executor">
119129
Execute operations on behalf of the account.
120130
</HelpTooltip>
121131
</label>
122-
<label class:checked={opts.executor === 'delayed'} class="subcontrol">
132+
<label class:checked={opts.executor?.delayed} class="subcontrol">
123133
<input
124134
type="checkbox"
125-
checked={opts.executor === 'delayed'}
135+
checked={opts.executor?.delayed}
126136
on:change={e => {
127-
if (e.currentTarget?.checked) opts.executor = 'delayed';
128-
else opts.executor = true;
137+
if (e.currentTarget?.checked) {
138+
opts.executor ??= {};
139+
opts.executor.delayed = {};
140+
} else opts.executor.delayed = false;
129141
}}
130142
/>
131143
Delayed
@@ -162,6 +174,6 @@
162174
</div>
163175
</section>
164176

165-
<AccessControlSection bind:access={opts.access} required={false} disabled={!opts.executor} />
177+
<AccessControlSection bind:access={opts.access} required={false} disabled={!opts.executor && !opts.validator} />
166178

167179
<InfoSection bind:info={opts.info} />

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,7 @@
807807

808808
"@openzeppelin/community-contracts@https://github.com/OpenZeppelin/openzeppelin-community-contracts":
809809
version "0.0.1"
810-
resolved "https://github.com/OpenZeppelin/openzeppelin-community-contracts#de17c8ee4b0329867f7219fbc401707be9518ff1"
810+
resolved "https://github.com/OpenZeppelin/openzeppelin-community-contracts#524341c3f895f264a1758261bc61a443b91d2d4f"
811811

812812
"@openzeppelin/contracts-upgradeable@^5.3.0":
813813
version "5.3.0"

0 commit comments

Comments
 (0)