Skip to content

Commit 1afff59

Browse files
authored
feat: Remove LogicalOrWrapper payee support for ERC20 (#313)
* feat: remove logicalOrWrapperEnforcer and update payee validation for ERC20 permissions * feat: implement RedemptionForm component and integrate into index page
1 parent 73b0445 commit 1afff59

15 files changed

Lines changed: 532 additions & 113 deletions

File tree

packages/gator-permissions-snap/src/core/chainMetadata.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export type DelegationContracts = {
2020
allowedCalldataEnforcer: Hex;
2121
redeemerEnforcer: Hex;
2222
allowedTargetsEnforcer: Hex;
23-
logicalOrWrapperEnforcer: Hex;
2423
};
2524

2625
const contracts: DelegationContracts = {
@@ -39,7 +38,6 @@ const contracts: DelegationContracts = {
3938
allowedCalldataEnforcer: '0xc2b0d624c1c4319760C96503BA27C347F3260f55',
4039
redeemerEnforcer: '0xE144b0b2618071B4E56f746313528a669c7E65c5',
4140
allowedTargetsEnforcer: '0x7F20f61b1f09b08D970938F6fa563634d65c4EeB',
42-
logicalOrWrapperEnforcer: '0xE1302607a3251AF54c3a6e69318d6aa07F5eB46c',
4341
};
4442

4543
// derived from https://chainid.network/chains.json

packages/gator-permissions-snap/src/core/payeeCaveat.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import type { Caveat, Hex } from '@metamask/delegation-core';
44
import {
55
createAllowedCalldataTerms,
66
createAllowedTargetsTerms,
7-
createLogicalOrWrapperTerms,
87
} from '@metamask/delegation-core';
8+
import { InvalidInputError } from '@metamask/snaps-sdk';
99

1010
import type { DelegationContracts } from './chainMetadata';
11+
import { MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR } from '../permissions/validation';
1112

1213
const ERC20_PERMISSION_TYPES = new Set([
1314
'erc20-token-stream',
@@ -19,8 +20,6 @@ const NATIVE_PERMISSION_TYPES = new Set([
1920
'native-token-periodic',
2021
]);
2122

22-
const EMPTY_ARGS = new Uint8Array();
23-
2423
/**
2524
* Pads an Ethereum address to 32 bytes (left-padded with zeros).
2625
* @param address - The address to pad.
@@ -71,7 +70,7 @@ function buildNativePayeeCaveat(
7170
* Appends payee-restricting caveats when the permission request includes a payee rule.
7271
*
7372
* For native token permissions, allowedTargetsEnforcer supports multiple targets directly.
74-
* For ERC-20 permissions, multiple addresses are wrapped in a LogicalOrWrapperEnforcer.
73+
* For ERC-20 permissions, allowedCalldataEnforcer supports one target.
7574
*
7675
* @param options - Arguments for appending the caveat.
7776
* @param options.rules - Resolved permission request rules from the grant flow.
@@ -106,17 +105,7 @@ export function appendPayeeCaveatIfPresent({
106105
}
107106

108107
if (isErc20 && rawAddresses.length > 1) {
109-
const caveatGroups = rawAddresses.map((address) => {
110-
const caveat = buildErc20PayeeCaveat(address, contracts);
111-
return [{ ...caveat, args: EMPTY_ARGS }];
112-
});
113-
114-
caveats.push({
115-
enforcer: contracts.logicalOrWrapperEnforcer,
116-
terms: createLogicalOrWrapperTerms({ caveatGroups }),
117-
args: '0x',
118-
});
119-
return;
108+
throw new InvalidInputError(MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR);
120109
}
121110

122111
const payeeCaveat = isErc20

packages/gator-permissions-snap/src/permissions/erc20TokenPeriodic/validation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function validatePermissionData(
3636

3737
validateStartTime(startTime, rules);
3838
validateRedeemerRule(rules);
39-
validatePayeeRule(rules);
39+
validatePayeeRule(rules, { allowMultiplePayees: false });
4040

4141
return true;
4242
}

packages/gator-permissions-snap/src/permissions/erc20TokenStream/validation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function validatePermissionData(
5757

5858
validateStartTime(startTime, rules);
5959
validateRedeemerRule(rules);
60-
validatePayeeRule(rules);
60+
validatePayeeRule(rules, { allowMultiplePayees: false });
6161

6262
return true;
6363
}

packages/gator-permissions-snap/src/permissions/validation.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { extractDescriptorName } from '@metamask/7715-permissions-shared/utils';
33
import type { Hex } from '@metamask/delegation-core';
44
import { InvalidInputError } from '@metamask/snaps-sdk';
55

6+
export const MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR =
7+
'Multiple payee addresses are not currently supported for ERC20 permissions.';
8+
69
/**
710
* Validates a hex integer value with configurable constraints.
811
* @param params - The validation parameters.
@@ -69,10 +72,17 @@ export function validateRedeemerRule(
6972
/**
7073
* Validates the payee rule, if present, has a non-empty addresses array.
7174
* @param rules - The rules of the permission request.
75+
* @param options - Payee validation options.
76+
* @param options.allowMultiplePayees - Whether more than one payee address is supported.
7277
* @throws {InvalidInputError} If a payee rule exists with missing or empty addresses.
7378
*/
7479
export function validatePayeeRule(
7580
rules: PermissionRequest['rules'] | undefined,
81+
{
82+
allowMultiplePayees = true,
83+
}: {
84+
allowMultiplePayees?: boolean;
85+
} = {},
7686
): void {
7787
const payeeRule = rules?.find(
7888
(rule) => extractDescriptorName(rule.type) === 'payee',
@@ -87,6 +97,10 @@ export function validatePayeeRule(
8797
'Invalid payee rule: must include a non-empty addresses array',
8898
);
8999
}
100+
101+
if (!allowMultiplePayees && addresses.length > 1) {
102+
throw new InvalidInputError(MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR);
103+
}
90104
}
91105

92106
/**

packages/gator-permissions-snap/test/core/chainMetadata.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ describe('chainMetadata', () => {
3535
allowedCalldataEnforcer: expect.any(String),
3636
redeemerEnforcer: expect.any(String),
3737
allowedTargetsEnforcer: expect.any(String),
38-
logicalOrWrapperEnforcer: expect.any(String),
3938
},
4039
name: expect.any(String),
4140
explorerUrl: expect.any(String),
@@ -105,7 +104,6 @@ describe('chainMetadata', () => {
105104
'allowedCalldataEnforcer',
106105
'redeemerEnforcer',
107106
'allowedTargetsEnforcer',
108-
'logicalOrWrapperEnforcer',
109107
];
110108

111109
const metadata = getChainMetadata({ chainId: 11155111 }); // Sepolia

packages/gator-permissions-snap/test/core/payeeCaveat.test.ts

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import type { Caveat, Hex } from '@metamask/delegation-core';
22
import {
33
createAllowedCalldataTerms,
44
createAllowedTargetsTerms,
5-
createLogicalOrWrapperTerms,
65
} from '@metamask/delegation-core';
76

87
import { appendPayeeCaveatIfPresent } from '../../src/core/payeeCaveat';
8+
import { MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR } from '../../src/permissions/validation';
99

1010
const MOCK_CONTRACTS = {
1111
delegationManager: '0x0000000000000000000000000000000000000001' as Hex,
@@ -26,12 +26,10 @@ const MOCK_CONTRACTS = {
2626
allowedCalldataEnforcer: '0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' as Hex,
2727
redeemerEnforcer: '0x000000000000000000000000000000000000000c' as Hex,
2828
allowedTargetsEnforcer: '0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' as Hex,
29-
logicalOrWrapperEnforcer: '0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC' as Hex,
3029
};
3130

3231
const PAYEE_ADDRESS_1 = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' as Hex;
3332
const PAYEE_ADDRESS_2 = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' as Hex;
34-
const EMPTY_ARGS = new Uint8Array();
3533

3634
/**
3735
* Pads an Ethereum address to 32 bytes (left-padded with zeros).
@@ -117,49 +115,23 @@ describe('appendPayeeCaveatIfPresent', () => {
117115
},
118116
);
119117

120-
it('wraps multiple payees in logicalOrWrapperEnforcer for ERC-20', () => {
118+
it('throws for multiple ERC-20 payees', () => {
121119
const caveats: Caveat[] = [];
122-
appendPayeeCaveatIfPresent({
123-
rules: [
124-
{
125-
type: 'payee',
126-
data: { addresses: [PAYEE_ADDRESS_1, PAYEE_ADDRESS_2] },
127-
},
128-
],
129-
contracts: MOCK_CONTRACTS,
130-
caveats,
131-
permissionType: 'erc20-token-stream',
132-
});
133-
134-
expect(caveats).toHaveLength(1);
135-
expect(caveats[0].enforcer).toBe(MOCK_CONTRACTS.logicalOrWrapperEnforcer);
136-
expect(caveats[0].terms).toStrictEqual(
137-
createLogicalOrWrapperTerms({
138-
caveatGroups: [
139-
[
140-
{
141-
enforcer: MOCK_CONTRACTS.allowedCalldataEnforcer,
142-
terms: createAllowedCalldataTerms({
143-
startIndex: 4,
144-
value: padTo32Bytes(PAYEE_ADDRESS_1),
145-
}),
146-
args: EMPTY_ARGS,
147-
},
148-
],
149-
[
150-
{
151-
enforcer: MOCK_CONTRACTS.allowedCalldataEnforcer,
152-
terms: createAllowedCalldataTerms({
153-
startIndex: 4,
154-
value: padTo32Bytes(PAYEE_ADDRESS_2),
155-
}),
156-
args: EMPTY_ARGS,
157-
},
158-
],
120+
expect(() =>
121+
appendPayeeCaveatIfPresent({
122+
rules: [
123+
{
124+
type: 'payee',
125+
data: { addresses: [PAYEE_ADDRESS_1, PAYEE_ADDRESS_2] },
126+
},
159127
],
128+
contracts: MOCK_CONTRACTS,
129+
caveats,
130+
permissionType: 'erc20-token-stream',
160131
}),
161-
);
162-
expect(caveats[0].args).toBe('0x');
132+
).toThrow(MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR);
133+
134+
expect(caveats).toHaveLength(0);
163135
});
164136
});
165137

packages/gator-permissions-snap/test/permissions/erc20TokenPeriodic/validation.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { bigIntToHex } from '@metamask/utils';
44
import { TimePeriod } from '../../../src/core/types';
55
import type { Erc20TokenPeriodicPermissionRequest } from '../../../src/permissions/erc20TokenPeriodic/types';
66
import { parseAndValidatePermission } from '../../../src/permissions/erc20TokenPeriodic/validation';
7+
import { MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR } from '../../../src/permissions/validation';
78
import { TIME_PERIOD_TO_SECONDS } from '../../../src/utils/time';
89
import { parseUnits } from '../../../src/utils/value';
910

@@ -57,6 +58,49 @@ describe('erc20TokenPeriodic:validation', () => {
5758
).not.toThrow();
5859
});
5960

61+
describe('payee rule validation', () => {
62+
it('allows one payee', () => {
63+
const singlePayeeRequest = {
64+
...validPermissionRequest,
65+
rules: [
66+
...validPermissionRequest.rules,
67+
{
68+
type: 'payee',
69+
data: {
70+
addresses: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'],
71+
},
72+
},
73+
],
74+
};
75+
76+
expect(() =>
77+
parseAndValidatePermission(singlePayeeRequest),
78+
).not.toThrow();
79+
});
80+
81+
it('throws for multiple payees', () => {
82+
const multiPayeeRequest = {
83+
...validPermissionRequest,
84+
rules: [
85+
...validPermissionRequest.rules,
86+
{
87+
type: 'payee',
88+
data: {
89+
addresses: [
90+
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
91+
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
92+
],
93+
},
94+
},
95+
],
96+
};
97+
98+
expect(() => parseAndValidatePermission(multiPayeeRequest)).toThrow(
99+
MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR,
100+
);
101+
});
102+
});
103+
60104
it('should throw for invalid permission type', () => {
61105
const invalidTypeRequest = {
62106
...validPermissionRequest,

packages/gator-permissions-snap/test/permissions/erc20TokenStream/validation.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { bigIntToHex } from '@metamask/utils';
33

44
import type { Erc20TokenStreamPermissionRequest } from '../../../src/permissions/erc20TokenStream/types';
55
import { parseAndValidatePermission } from '../../../src/permissions/erc20TokenStream/validation';
6+
import { MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR } from '../../../src/permissions/validation';
67
import { parseUnits } from '../../../src/utils/value';
78

89
const tokenDecimals = 10;
@@ -60,6 +61,49 @@ describe('erc20TokenStream:validation', () => {
6061
).not.toThrow();
6162
});
6263

64+
describe('payee rule validation', () => {
65+
it('allows one payee', () => {
66+
const singlePayeeRequest = {
67+
...validPermissionRequest,
68+
rules: [
69+
...validPermissionRequest.rules,
70+
{
71+
type: 'payee',
72+
data: {
73+
addresses: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'],
74+
},
75+
},
76+
],
77+
};
78+
79+
expect(() =>
80+
parseAndValidatePermission(singlePayeeRequest),
81+
).not.toThrow();
82+
});
83+
84+
it('throws for multiple payees', () => {
85+
const multiPayeeRequest = {
86+
...validPermissionRequest,
87+
rules: [
88+
...validPermissionRequest.rules,
89+
{
90+
type: 'payee',
91+
data: {
92+
addresses: [
93+
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
94+
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
95+
],
96+
},
97+
},
98+
],
99+
};
100+
101+
expect(() => parseAndValidatePermission(multiPayeeRequest)).toThrow(
102+
MULTIPLE_ERC20_PAYEES_UNSUPPORTED_ERROR,
103+
);
104+
});
105+
});
106+
63107
it('should throw for invalid permission type', () => {
64108
const invalidTypeRequest = {
65109
...validPermissionRequest,

packages/gator-permissions-snap/test/permissions/nativeTokenPeriodic/validation.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,30 @@ describe('nativeTokenPeriodic:validation', () => {
5252
).not.toThrow();
5353
});
5454

55+
describe('payee rule validation', () => {
56+
it('allows multiple payees', () => {
57+
const multiPayeeRequest = {
58+
...validPermissionRequest,
59+
rules: [
60+
...validPermissionRequest.rules,
61+
{
62+
type: 'payee',
63+
data: {
64+
addresses: [
65+
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
66+
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
67+
],
68+
},
69+
},
70+
],
71+
};
72+
73+
expect(() =>
74+
parseAndValidatePermission(multiPayeeRequest),
75+
).not.toThrow();
76+
});
77+
});
78+
5579
it('should throw for invalid permission type', () => {
5680
const invalidTypeRequest = {
5781
...validPermissionRequest,

0 commit comments

Comments
 (0)