Skip to content

Commit c70e131

Browse files
authored
Merge pull request #63 from MetaMask/refactor/improve-delegation-devex
Improve declarative delegation interface
2 parents 75e6005 + 63c03c1 commit c70e131

27 files changed

Lines changed: 1530 additions & 98 deletions

packages/delegation-toolkit/src/caveatBuilder/resolveCaveats.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export type Caveats = CaveatBuilder | (Caveat | CoreCaveatConfiguration)[];
1010
* @param config - The configuration for the caveat builder.
1111
* @param config.environment - The environment to be used for the caveat builder.
1212
* @param config.scope - The scope to be used for the caveat builder.
13-
* @param config.caveats - The caveats to be resolved, which can be either a CaveatBuilder or an array of Caveat or CaveatConfiguration.
13+
* @param config.caveats - The caveats to be resolved, which can be either a CaveatBuilder or an array of Caveat or CaveatConfiguration. Optional - if not provided, only scope caveats will be used.
1414
* @returns The resolved array of caveats.
1515
*/
1616
export const resolveCaveats = ({
@@ -20,27 +20,29 @@ export const resolveCaveats = ({
2020
}: {
2121
environment: DeleGatorEnvironment;
2222
scope: ScopeConfig;
23-
caveats: Caveats;
23+
caveats?: Caveats;
2424
}) => {
2525
const scopeCaveatBuilder = createCaveatBuilderFromScope(environment, scope);
2626

27-
if ('build' in caveats && typeof caveats.build === 'function') {
28-
(caveats as CaveatBuilder).build().forEach((caveat) => {
29-
scopeCaveatBuilder.addCaveat(caveat);
30-
});
31-
} else if (Array.isArray(caveats)) {
32-
caveats.forEach((caveat) => {
33-
try {
34-
if ('type' in caveat) {
35-
const { type, ...config } = caveat;
36-
scopeCaveatBuilder.addCaveat(type, config);
37-
} else {
38-
scopeCaveatBuilder.addCaveat(caveat);
27+
if (caveats) {
28+
if ('build' in caveats && typeof caveats.build === 'function') {
29+
(caveats as CaveatBuilder).build().forEach((caveat) => {
30+
scopeCaveatBuilder.addCaveat(caveat);
31+
});
32+
} else if (Array.isArray(caveats)) {
33+
caveats.forEach((caveat) => {
34+
try {
35+
if ('type' in caveat) {
36+
const { type, ...config } = caveat;
37+
scopeCaveatBuilder.addCaveat(type, config);
38+
} else {
39+
scopeCaveatBuilder.addCaveat(caveat);
40+
}
41+
} catch (error) {
42+
throw new Error(`Invalid caveat: ${(error as Error).message}`);
3943
}
40-
} catch (error) {
41-
throw new Error(`Invalid caveat: ${(error as Error).message}`);
42-
}
43-
});
44+
});
45+
}
4446
}
4547

4648
return scopeCaveatBuilder.build();

packages/delegation-toolkit/src/caveatBuilder/scope/functionCallScope.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const isFunctionCallConfig = (
3131
* @param config - Configuration object containing allowed targets, methods, and optionally calldata.
3232
* @returns A configured caveat builder with the specified caveats.
3333
* @throws Error if any of the required parameters are invalid.
34+
* @throws Error if both allowedCalldata and exactCalldata are provided simultaneously.
3435
*/
3536
export function createFunctionCallCaveatBuilder(
3637
environment: DeleGatorEnvironment,
@@ -42,16 +43,21 @@ export function createFunctionCallCaveatBuilder(
4243
throw new Error('Invalid Function Call configuration');
4344
}
4445

46+
if (allowedCalldata && allowedCalldata.length > 0 && exactCalldata) {
47+
throw new Error(
48+
'Cannot specify both allowedCalldata and exactCalldata. Please use only one calldata restriction type.',
49+
);
50+
}
51+
4552
const caveatBuilder = createCaveatBuilder(environment)
4653
.addCaveat('allowedTargets', { targets })
4754
.addCaveat('allowedMethods', { selectors });
4855

49-
if (allowedCalldata) {
56+
if (allowedCalldata && allowedCalldata.length > 0) {
5057
allowedCalldata.forEach((calldataConfig) => {
5158
caveatBuilder.addCaveat('allowedCalldata', calldataConfig);
5259
});
53-
}
54-
if (exactCalldata) {
60+
} else if (exactCalldata) {
5561
caveatBuilder.addCaveat('exactCalldata', exactCalldata);
5662
}
5763

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import type { DeleGatorEnvironment } from '../../types';
2+
import type { AllowedCalldataBuilderConfig } from '../allowedCalldataBuilder';
23
import { createCaveatBuilder } from '../coreCaveatBuilder';
34
import type { CoreCaveatBuilder } from '../coreCaveatBuilder';
5+
import type { ExactCalldataBuilderConfig } from '../exactCalldataBuilder';
46
import type {
57
nativeTokenPeriodTransfer,
68
NativeTokenPeriodTransferBuilderConfig,
79
} from '../nativeTokenPeriodTransferBuilder';
810

911
export type NativeTokenPeriodicScopeConfig = {
1012
type: typeof nativeTokenPeriodTransfer;
13+
allowedCalldata?: AllowedCalldataBuilderConfig[];
14+
exactCalldata?: ExactCalldataBuilderConfig;
1115
} & NativeTokenPeriodTransferBuilderConfig;
1216

1317
/**
@@ -17,19 +21,49 @@ export type NativeTokenPeriodicScopeConfig = {
1721
* @param config - Configuration object containing native token periodic transfer parameters.
1822
* @returns A configured caveat builder with native token period transfer and exact calldata caveats.
1923
* @throws Error if any of the native token periodic transfer parameters are invalid.
24+
* @throws Error if both allowedCalldata and exactCalldata are provided simultaneously.
2025
* @throws Error if the environment is not properly configured.
2126
*/
2227
export function createNativeTokenPeriodicCaveatBuilder(
2328
environment: DeleGatorEnvironment,
2429
config: NativeTokenPeriodicScopeConfig,
2530
): CoreCaveatBuilder {
26-
return createCaveatBuilder(environment)
27-
.addCaveat('exactCalldata', {
31+
const {
32+
periodAmount,
33+
periodDuration,
34+
startDate,
35+
allowedCalldata,
36+
exactCalldata,
37+
} = config;
38+
39+
if (allowedCalldata && allowedCalldata.length > 0 && exactCalldata) {
40+
throw new Error(
41+
'Cannot specify both allowedCalldata and exactCalldata. Please use only one calldata restriction type.',
42+
);
43+
}
44+
45+
const caveatBuilder = createCaveatBuilder(environment);
46+
47+
// Add calldata restrictions
48+
if (allowedCalldata && allowedCalldata.length > 0) {
49+
allowedCalldata.forEach((calldataConfig) => {
50+
caveatBuilder.addCaveat('allowedCalldata', calldataConfig);
51+
});
52+
} else if (exactCalldata) {
53+
caveatBuilder.addCaveat('exactCalldata', exactCalldata);
54+
} else {
55+
// Default behavior: only allow empty calldata
56+
caveatBuilder.addCaveat('exactCalldata', {
2857
calldata: '0x',
29-
})
30-
.addCaveat('nativeTokenPeriodTransfer', {
31-
periodAmount: config.periodAmount,
32-
periodDuration: config.periodDuration,
33-
startDate: config.startDate,
3458
});
59+
}
60+
61+
// Add native token period transfer restriction
62+
caveatBuilder.addCaveat('nativeTokenPeriodTransfer', {
63+
periodAmount,
64+
periodDuration,
65+
startDate,
66+
});
67+
68+
return caveatBuilder;
3569
}
Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import type { DeleGatorEnvironment } from '../../types';
2+
import type { AllowedCalldataBuilderConfig } from '../allowedCalldataBuilder';
23
import { createCaveatBuilder } from '../coreCaveatBuilder';
34
import type { CoreCaveatBuilder } from '../coreCaveatBuilder';
5+
import type { ExactCalldataBuilderConfig } from '../exactCalldataBuilder';
46
import type {
57
nativeTokenStreaming,
68
NativeTokenStreamingBuilderConfig,
79
} from '../nativeTokenStreamingBuilder';
810

911
export type NativeTokenStreamingScopeConfig = {
1012
type: typeof nativeTokenStreaming;
13+
allowedCalldata?: AllowedCalldataBuilderConfig[];
14+
exactCalldata?: ExactCalldataBuilderConfig;
1115
} & NativeTokenStreamingBuilderConfig;
1216

1317
/**
@@ -17,20 +21,51 @@ export type NativeTokenStreamingScopeConfig = {
1721
* @param config - Configuration object containing native token streaming parameters.
1822
* @returns A configured caveat builder with native token streaming and exact calldata caveats.
1923
* @throws Error if any of the native token streaming parameters are invalid.
24+
* @throws Error if both allowedCalldata and exactCalldata are provided simultaneously.
2025
* @throws Error if the environment is not properly configured.
2126
*/
2227
export function createNativeTokenStreamingCaveatBuilder(
2328
environment: DeleGatorEnvironment,
2429
config: NativeTokenStreamingScopeConfig,
2530
): CoreCaveatBuilder {
26-
return createCaveatBuilder(environment)
27-
.addCaveat('exactCalldata', {
31+
const {
32+
initialAmount,
33+
maxAmount,
34+
amountPerSecond,
35+
startTime,
36+
allowedCalldata,
37+
exactCalldata,
38+
} = config;
39+
40+
if (allowedCalldata && allowedCalldata.length > 0 && exactCalldata) {
41+
throw new Error(
42+
'Cannot specify both allowedCalldata and exactCalldata. Please use only one calldata restriction type.',
43+
);
44+
}
45+
46+
const caveatBuilder = createCaveatBuilder(environment);
47+
48+
// Add calldata restrictions
49+
if (allowedCalldata && allowedCalldata.length > 0) {
50+
allowedCalldata.forEach((calldataConfig) => {
51+
caveatBuilder.addCaveat('allowedCalldata', calldataConfig);
52+
});
53+
} else if (exactCalldata) {
54+
caveatBuilder.addCaveat('exactCalldata', exactCalldata);
55+
} else {
56+
// Default behavior: only allow empty calldata
57+
caveatBuilder.addCaveat('exactCalldata', {
2858
calldata: '0x',
29-
})
30-
.addCaveat('nativeTokenStreaming', {
31-
initialAmount: config.initialAmount,
32-
maxAmount: config.maxAmount,
33-
amountPerSecond: config.amountPerSecond,
34-
startTime: config.startTime,
3559
});
60+
}
61+
62+
// Add native token streaming restriction
63+
caveatBuilder.addCaveat('nativeTokenStreaming', {
64+
initialAmount,
65+
maxAmount,
66+
amountPerSecond,
67+
startTime,
68+
});
69+
70+
return caveatBuilder;
3671
}
Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import type { DeleGatorEnvironment } from '../../types';
2+
import type { AllowedCalldataBuilderConfig } from '../allowedCalldataBuilder';
23
import { createCaveatBuilder } from '../coreCaveatBuilder';
34
import type { CoreCaveatBuilder } from '../coreCaveatBuilder';
5+
import type { ExactCalldataBuilderConfig } from '../exactCalldataBuilder';
46
import type {
57
nativeTokenTransferAmount,
68
NativeTokenTransferAmountBuilderConfig,
79
} from '../nativeTokenTransferAmountBuilder';
810

911
export type NativeTokenTransferScopeConfig = {
1012
type: typeof nativeTokenTransferAmount;
13+
allowedCalldata?: AllowedCalldataBuilderConfig[];
14+
exactCalldata?: ExactCalldataBuilderConfig;
1115
} & NativeTokenTransferAmountBuilderConfig;
1216

1317
/**
@@ -17,17 +21,41 @@ export type NativeTokenTransferScopeConfig = {
1721
* @param config - Configuration object containing native token transfer parameters.
1822
* @returns A configured caveat builder with native token transfer amount and exact calldata caveats.
1923
* @throws Error if any of the native token transfer parameters are invalid.
24+
* @throws Error if both allowedCalldata and exactCalldata are provided simultaneously.
2025
* @throws Error if the environment is not properly configured.
2126
*/
2227
export function createNativeTokenTransferCaveatBuilder(
2328
environment: DeleGatorEnvironment,
2429
config: NativeTokenTransferScopeConfig,
2530
): CoreCaveatBuilder {
26-
return createCaveatBuilder(environment)
27-
.addCaveat('exactCalldata', {
31+
const { maxAmount, allowedCalldata, exactCalldata } = config;
32+
33+
if (allowedCalldata && allowedCalldata.length > 0 && exactCalldata) {
34+
throw new Error(
35+
'Cannot specify both allowedCalldata and exactCalldata. Please use only one calldata restriction type.',
36+
);
37+
}
38+
39+
const caveatBuilder = createCaveatBuilder(environment);
40+
41+
// Add calldata restrictions
42+
if (allowedCalldata && allowedCalldata.length > 0) {
43+
allowedCalldata.forEach((calldataConfig) => {
44+
caveatBuilder.addCaveat('allowedCalldata', calldataConfig);
45+
});
46+
} else if (exactCalldata) {
47+
caveatBuilder.addCaveat('exactCalldata', exactCalldata);
48+
} else {
49+
// Default behavior: only allow empty calldata
50+
caveatBuilder.addCaveat('exactCalldata', {
2851
calldata: '0x',
29-
})
30-
.addCaveat('nativeTokenTransferAmount', {
31-
maxAmount: config.maxAmount,
3252
});
53+
}
54+
55+
// Add native token transfer amount restriction
56+
caveatBuilder.addCaveat('nativeTokenTransferAmount', {
57+
maxAmount,
58+
});
59+
60+
return caveatBuilder;
3361
}

packages/delegation-toolkit/src/delegation.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,9 @@ type BaseCreateDelegationOptions = {
185185
environment: DeleGatorEnvironment;
186186
scope: ScopeConfig;
187187
from: Hex;
188-
caveats: Caveats;
188+
caveats?: Caveats;
189189
parentDelegation?: Delegation | Hex;
190190
salt?: Hex;
191-
allowInsecureUnrestrictedDelegation?: boolean;
192191
};
193192

194193
/**

packages/delegation-toolkit/test/DelegationFramework/DelegationManager/delegationManagement.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ describe('DelegationManager - Delegation Management', () => {
9191
targets: [alice.address],
9292
selectors: ['0x00000000'],
9393
},
94-
caveats: [],
9594
});
9695

9796
const encodedData = DelegationManager.encode.disableDelegation({
@@ -117,7 +116,6 @@ describe('DelegationManager - Delegation Management', () => {
117116
targets: [alice.address],
118117
selectors: ['0x00000000'],
119118
},
120-
caveats: [],
121119
});
122120

123121
const encodedData = DelegationManager.encode.enableDelegation({
@@ -143,7 +141,6 @@ describe('DelegationManager - Delegation Management', () => {
143141
targets: [alice.address],
144142
selectors: ['0x00000000'],
145143
},
146-
caveats: [],
147144
});
148145

149146
const execution = createExecution({

packages/delegation-toolkit/test/caveatBuilder/scope/nativeTokenPeriodicScope.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe('createNativeTokenPeriodicCaveatBuilder', () => {
1010
const environment = {
1111
caveatEnforcers: {
1212
ExactCalldataEnforcer: randomAddress(),
13+
AllowedCalldataEnforcer: randomAddress(),
1314
NativeTokenPeriodTransferEnforcer: randomAddress(),
1415
},
1516
} as unknown as DeleGatorEnvironment;
@@ -46,4 +47,38 @@ describe('createNativeTokenPeriodicCaveatBuilder', () => {
4647
},
4748
]);
4849
});
50+
51+
it('creates a native token periodic transfer CaveatBuilder with empty allowedCalldata array (should fall back to default)', () => {
52+
const config: NativeTokenPeriodicScopeConfig = {
53+
type: 'nativeTokenPeriodTransfer',
54+
periodAmount: 1000n,
55+
periodDuration: 1000,
56+
startDate: Math.floor(Date.now() / 1000),
57+
allowedCalldata: [], // Empty array should trigger fallback to default exactCalldata
58+
};
59+
60+
const caveatBuilder = createNativeTokenPeriodicCaveatBuilder(
61+
environment,
62+
config,
63+
);
64+
65+
const caveats = caveatBuilder.build();
66+
67+
expect(caveats).to.deep.equal([
68+
{
69+
enforcer: environment.caveatEnforcers.ExactCalldataEnforcer,
70+
args: '0x',
71+
terms: '0x',
72+
},
73+
{
74+
enforcer: environment.caveatEnforcers.NativeTokenPeriodTransferEnforcer,
75+
args: '0x',
76+
terms: concat([
77+
toHex(config.periodAmount, { size: 32 }),
78+
toHex(config.periodDuration, { size: 32 }),
79+
toHex(config.startDate, { size: 32 }),
80+
]),
81+
},
82+
]);
83+
});
4984
});

0 commit comments

Comments
 (0)