Skip to content

Commit 8c96a52

Browse files
authored
feat: accept delegation chain as either encoded Hex or decoded Delegation[] (#140)
* Standardize delegations and permissionContext parameters - Adds new PermissionContext type that describes either a decoded Delegation[] or abi encoded Hex string, added to public API - Rename permissionsContext parameter in ERC7710 action to permissionContext (singular) - Accept PermissionContext in ERC-7710 actions and DelegationManager redeem paths - encodeDelegations/decodeDelegations now both accept PermissionContext * Remove unused internal functions - prepDelegationHashForPasskeySign - getCaveatArrayPacketHash - deployWithSimpleFactory * Update CHANGELOG.md * Add test coverage for encodeDelegations and decodeDelegations inputs that won't get transformed. Added validation for invalid Hex inputs. * Override environment and use expected DelegationManager address in new tests * Linting
1 parent a38dbf5 commit 8c96a52

15 files changed

Lines changed: 560 additions & 114 deletions

File tree

packages/delegator-e2e/test/actions/erc7710sendTransactionWithDelegation.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ let aliceSmartAccount: MetaMaskSmartAccount<Implementation.Hybrid>;
3838
let bob: Account;
3939
let bobPrivateKey: Hex;
4040
let aliceCounterContractAddress: Address;
41-
let permissionsContext: Hex;
41+
let permissionContext: Hex;
4242
let signedDelegation: Delegation;
4343

4444
beforeEach(async () => {
@@ -75,7 +75,7 @@ beforeEach(async () => {
7575
signature: await aliceSmartAccount.signDelegation({ delegation }),
7676
};
7777

78-
permissionsContext = encodeDelegations([signedDelegation]);
78+
permissionContext = encodeDelegations([signedDelegation]);
7979
});
8080

8181
/*
@@ -113,7 +113,7 @@ test('maincase: Bob redeems the delegation in order to increment() on the counte
113113
abi: CounterMetadata.abi,
114114
functionName: 'increment',
115115
}),
116-
permissionsContext,
116+
permissionContext,
117117
delegationManager,
118118
});
119119

@@ -183,7 +183,7 @@ test('Bob redelegates to Carol, who redeems the delegation to call increment() o
183183
abi: CounterMetadata.abi,
184184
functionName: 'increment',
185185
}),
186-
permissionsContext: redelegatedPermissionsContext,
186+
permissionContext: redelegatedPermissionsContext,
187187
delegationManager,
188188
},
189189
);
@@ -253,7 +253,7 @@ test('Bob sends a native value transaction with delegation', async () => {
253253
signature: await aliceSmartAccount.signDelegation({ delegation }),
254254
};
255255

256-
const permissionsContext = encodeDelegations([signedDelegation]);
256+
const permissionContext = encodeDelegations([signedDelegation]);
257257

258258
const bobWalletClient = createWalletClient({
259259
account: bob,
@@ -268,7 +268,7 @@ test('Bob sends a native value transaction with delegation', async () => {
268268
chain,
269269
to: recipient,
270270
value: maxAmount,
271-
permissionsContext,
271+
permissionContext,
272272
delegationManager,
273273
});
274274

packages/delegator-e2e/test/actions/erc7710sendUserOperationWithDelegation.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { expectUserOperationToSucceed } from '../utils/assertions';
3434
let aliceSmartAccount: MetaMaskSmartAccount<Implementation.Hybrid>;
3535
let bobSmartAccount: MetaMaskSmartAccount<Implementation.Hybrid>;
3636
let aliceCounterContractAddress: Address;
37-
let permissionsContext: Hex;
37+
let permissionContext: Hex;
3838
let signedDelegation: Delegation;
3939

4040
const bundlerClient = sponsoredBundlerClient.extend(erc7710BundlerActions());
@@ -81,7 +81,7 @@ beforeEach(async () => {
8181
signature: await aliceSmartAccount.signDelegation({ delegation }),
8282
};
8383

84-
permissionsContext = encodeDelegations([signedDelegation]);
84+
permissionContext = encodeDelegations([signedDelegation]);
8585
});
8686

8787
/*
@@ -114,7 +114,7 @@ test('maincase: Bob redeems the delegation in order to call increment() on the c
114114
functionName: 'increment',
115115
}),
116116
value: 0n,
117-
permissionsContext,
117+
permissionContext,
118118
delegationManager: aliceSmartAccount.environment.DelegationManager,
119119
},
120120
],
@@ -158,7 +158,7 @@ test('Bob redeems the delegation in order to call increment() on the counter con
158158
functionName: 'increment',
159159
}),
160160
value: 0n,
161-
permissionsContext,
161+
permissionContext,
162162
delegationManager: aliceSmartAccount.environment.DelegationManager,
163163
},
164164
{
@@ -217,7 +217,7 @@ test('Bob redeems the delegation, and deploys Alices smart account via dependenc
217217
functionName: 'increment',
218218
}),
219219
value: 0n,
220-
permissionsContext,
220+
permissionContext,
221221
delegationManager: aliceSmartAccount.environment.DelegationManager,
222222
},
223223
],
@@ -268,7 +268,7 @@ test('Bob redeems the delegation, with dependencies, even though Alices account
268268
functionName: 'increment',
269269
}),
270270
value: 0n,
271-
permissionsContext,
271+
permissionContext,
272272
delegationManager: aliceSmartAccount.environment.DelegationManager,
273273
},
274274
],

packages/delegator-e2e/test/caveats/nativeTokenPayment.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ test('maincase: Bob redeems the delegation with a permissions context allowing p
117117
}),
118118
};
119119

120-
const permissionsContext = encodeDelegations([signedPaymentDelegation]);
120+
const permissionContext = encodeDelegations([signedPaymentDelegation]);
121121

122122
await runTest_expectSuccess(
123123
delegationRequiringNativeTokenPayment,
124-
permissionsContext,
124+
permissionContext,
125125
recipient,
126126
requiredValue,
127127
);
@@ -164,11 +164,11 @@ test('Bob attempts to redeem the delegation without an argsEqualityCheckEnforcer
164164
}),
165165
};
166166

167-
const permissionsContext = encodeDelegations([signedPaymentDelegation]);
167+
const permissionContext = encodeDelegations([signedPaymentDelegation]);
168168

169169
await runTest_expectFailure(
170170
delegationRequiringNativeTokenPayment,
171-
permissionsContext,
171+
permissionContext,
172172
recipient,
173173
'NativeTokenPaymentEnforcer:missing-argsEqualityCheckEnforcer',
174174
);
@@ -192,11 +192,11 @@ test('Bob attempts to redeem the delegation without providing a valid permission
192192
signature: '0x',
193193
};
194194

195-
const permissionsContext = '0x' as const;
195+
const permissionContext = '0x' as const;
196196

197197
await runTest_expectFailure(
198198
delegationRequiringNativeTokenPayment,
199-
permissionsContext,
199+
permissionContext,
200200
recipient,
201201
undefined, // The NativeTokenPaymentEnforcer rejects when it fails to decode the permissions context
202202
);
@@ -251,11 +251,11 @@ test('Bob attempts to redeem with invalid terms length', async () => {
251251
}),
252252
};
253253

254-
const permissionsContext = encodeDelegations([signedPaymentDelegation]);
254+
const permissionContext = encodeDelegations([signedPaymentDelegation]);
255255

256256
await runTest_expectFailure(
257257
delegationRequiringNativeTokenPayment,
258-
permissionsContext,
258+
permissionContext,
259259
recipient,
260260
'NativeTokenPaymentEnforcer:invalid-terms-length',
261261
);
@@ -281,27 +281,27 @@ test('Bob attempts to redeem with empty allowance delegations', async () => {
281281
};
282282

283283
// Create empty allowance delegations array
284-
const permissionsContext = encodeDelegations([]);
284+
const permissionContext = encodeDelegations([]);
285285

286286
await runTest_expectFailure(
287287
delegationRequiringNativeTokenPayment,
288-
permissionsContext,
288+
permissionContext,
289289
recipient,
290290
'NativeTokenPaymentEnforcer:invalid-allowance-delegations-length',
291291
);
292292
});
293293

294294
const runTest_expectSuccess = async (
295295
delegation: Delegation,
296-
permissionsContext: Hex,
296+
permissionContext: Hex,
297297
recipient: Address,
298298
requiredValue: bigint,
299299
) => {
300300
const balanceBefore = await publicClient.getBalance({
301301
address: recipient,
302302
});
303303

304-
const userOpHash = await submitUserOpForTest(delegation, permissionsContext);
304+
const userOpHash = await submitUserOpForTest(delegation, permissionContext);
305305

306306
const receipt = await sponsoredBundlerClient.waitForUserOperationReceipt({
307307
hash: userOpHash,
@@ -321,7 +321,7 @@ const runTest_expectSuccess = async (
321321

322322
const runTest_expectFailure = async (
323323
delegation: Delegation,
324-
permissionsContext: Hex,
324+
permissionContext: Hex,
325325
recipient: Address,
326326
expectedError: string | undefined,
327327
) => {
@@ -330,7 +330,7 @@ const runTest_expectFailure = async (
330330
});
331331

332332
const rejects = expect(
333-
submitUserOpForTest(delegation, permissionsContext),
333+
submitUserOpForTest(delegation, permissionContext),
334334
).rejects;
335335

336336
if (expectedError) {
@@ -350,7 +350,7 @@ const runTest_expectFailure = async (
350350

351351
const submitUserOpForTest = async (
352352
delegation: Delegation,
353-
permissionsContext: Hex,
353+
permissionContext: Hex,
354354
) => {
355355
const signedDelegation = {
356356
...delegation,
@@ -359,7 +359,7 @@ const submitUserOpForTest = async (
359359

360360
// we need to assign the permissions context to the caveat in order for it to process the payment
361361
// here we assume that the first caveat is the nativeTokenPayment caveat
362-
signedDelegation.caveats[0].args = permissionsContext;
362+
signedDelegation.caveats[0].args = permissionContext;
363363

364364
const execution = createExecution({
365365
target: zeroAddress,

packages/smart-accounts-kit/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Introduce `PermissionContext` to represent a delegation chain (ABI-encoded `Hex` or decoded `Delegation[]`). ([#140](https://github.com/MetaMask/smart-accounts-kit/pull/140))
13+
- **Breaking**: Replace usages of raw `Hex` _or_ `Delegation[]` with `PermissionContext`, and rename `permissionsContext` to `permissionContext` (note the singular "permission") where applicable:
14+
- `SendTransactionWithDelegation`: `permissionsContext: Hex``permissionContext: PermissionContext`
15+
- `SendUserOperationWithDelegation`: within `calls: DelegatedCall`, `permissionsContext: Hex``permissionContext: PermissionContext`
16+
- `redeemDelegations`: parameter `Delegation[]``PermissionContext`
17+
- `encodeDelegations` and `decodeDelegations` now accept `PermissionContext` (if the input is already the expected type, the input is returned)
18+
- `encode`, `execute`, and `simulate` functions for `DelegationManager.redeemDelegations` from `@metamask/smart-accounts-kit/contracts`: parameter `delegations: Delegation[]``delegations: PermissionContext`
19+
1020
### Fixed
1121

1222
- Allow scope type to be specified either as `ScopeType` enum, or string literal ([#133](https://github.com/MetaMask/smart-accounts-kit/pull/133))

packages/smart-accounts-kit/src/DelegationFramework/DelegationManager/methods/redeemDelegations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { simulateContract, writeContract } from 'viem/actions';
66
import { encodeDelegations } from '../../../delegation';
77
import { encodeExecutionCalldatas } from '../../../executions';
88
import type { ExecutionMode, ExecutionStruct } from '../../../executions';
9-
import type { Delegation } from '../../../types';
9+
import type { PermissionContext } from '../../../types';
1010
import type { InitializedClient } from '../../types';
1111

1212
export type EncodeRedeemDelegationsParameters = {
13-
delegations: Delegation[][];
13+
delegations: PermissionContext[];
1414
modes: ExecutionMode[];
1515
executions: ExecutionStruct[][];
1616
};

packages/smart-accounts-kit/src/actions/erc7710RedeemDelegationAction.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@ import type {
1717
SmartAccount,
1818
} from 'viem/account-abstraction';
1919

20+
import { encodeDelegations } from '../delegation';
2021
import {
2122
createExecution,
2223
encodeExecutionCalldatas,
2324
ExecutionMode,
2425
} from '../executions';
2526
import { getSmartAccountsEnvironment } from '../smartAccountsEnvironment';
26-
import type { Call } from '../types';
27+
import type { Call, PermissionContext } from '../types';
2728

2829
export type DelegatedCall = Call &
29-
OneOf<{ permissionsContext: Hex; delegationManager: Hex } | object>;
30+
OneOf<
31+
{ permissionContext: PermissionContext; delegationManager: Hex } | object
32+
>;
3033

3134
export type SendTransactionWithDelegationParameters<
3235
TChain extends Chain | undefined = Chain | undefined,
@@ -35,7 +38,7 @@ export type SendTransactionWithDelegationParameters<
3538
TRequest extends SendTransactionRequest<TChain, TChainOverride> =
3639
SendTransactionRequest<TChain, TChainOverride>,
3740
> = SendTransactionParameters<TChain, TAccount, TChainOverride, TRequest> & {
38-
permissionsContext: Hex;
41+
permissionContext: PermissionContext;
3942
delegationManager: Hex;
4043
};
4144

@@ -85,15 +88,15 @@ export async function sendTransactionWithDelegationAction<
8588
abi: DelegationManager,
8689
functionName: 'redeemDelegations',
8790
args: [
88-
[args.permissionsContext],
91+
[encodeDelegations(args.permissionContext)],
8992
[ExecutionMode.SingleDefault],
9093
encodeExecutionCalldatas([executions]),
9194
],
9295
});
9396

9497
const {
9598
value: _value,
96-
permissionsContext: _permissionsContext,
99+
permissionContext: _permissionContext,
97100
delegationManager: _delegationManager,
98101
...rest
99102
} = args;
@@ -141,7 +144,7 @@ export type SendUserOperationWithDelegationParameters<
141144
* functionName: 'increment',
142145
* }),
143146
* value: 0n,
144-
* permissionsContext: '0x...',
147+
* permissionContext: '0x...',
145148
* delegationManager: '0x...',
146149
* },
147150
* ],
@@ -215,6 +218,22 @@ export async function sendUserOperationWithDelegationAction<
215218
];
216219
}
217220

221+
parameters.calls = parameters.calls.map((call) => {
222+
if (!('permissionContext' in call)) {
223+
return call;
224+
}
225+
226+
const { permissionContext } = call;
227+
if (!permissionContext) {
228+
return call;
229+
}
230+
231+
return {
232+
...call,
233+
permissionContext: encodeDelegations(permissionContext),
234+
};
235+
});
236+
218237
return client.sendUserOperation(
219238
parameters as unknown as SendUserOperationParameters,
220239
);

packages/smart-accounts-kit/src/caveats.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
type Hex,
3-
encodePacked,
43
encodeAbiParameters,
54
parseAbiParameters,
65
keccak256,
@@ -33,22 +32,6 @@ export const getCaveatPacketHash = (input: Caveat): Hex => {
3332
return keccak256(encoded);
3433
};
3534

36-
/**
37-
* Calculates the hash of an array of Caveats.
38-
*
39-
* @param input - The array of Caveats.
40-
* @returns The keccak256 hash of the encoded Caveat array packet.
41-
*/
42-
export const getCaveatArrayPacketHash = (input: Caveat[]): Hex => {
43-
let encoded: Hex = '0x';
44-
45-
for (const caveat of input) {
46-
const caveatPacketHash = getCaveatPacketHash(caveat);
47-
encoded = encodePacked(['bytes', 'bytes32'], [encoded, caveatPacketHash]);
48-
}
49-
return keccak256(encoded);
50-
};
51-
5235
/**
5336
* Creates a caveat.
5437
*

0 commit comments

Comments
 (0)