Skip to content

Commit bc5b896

Browse files
authored
feat: adds utility functions to create caveats array for each permission type (#265)
* Implement caveat building logic for erc20tokenstream permission type * Add caveat building logic for remaining permission types * Add additional test coverage * Add CHANGELOG entry * create<PermissionType>Caveats functions are no longer asyncronous * Rename DeepRequired to Populated<BasePermission> and export in @metamask/7715-permission-types package Amend comment to reflect that both undefined and null are removed from union types. * Update all caveat builder functions to reflect validation in related validateAndDecode function * Rename MakePermissionDecoderConfig to PermissionDecoderCofnig, remove duplicate type
1 parent 25dfdc4 commit bc5b896

19 files changed

Lines changed: 1595 additions & 45 deletions

packages/7715-permission-types/CHANGELOG.md

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

1010
### Added
1111

12+
- Utility functions to create caveats array for each permission type ([#265](https://github.com/MetaMask/smart-accounts-kit/pull/265))
13+
- `createErc20TokenStreamCaveats()`
14+
- `createErc20TokenPeriodicCaveats()`
15+
- `createErc20TokenAllowanceCaveats()`
16+
- `createNativeTokenStreamCaveats()`
17+
- `createNativeTokenPeriodicCaveats()`
18+
- `createNativeTokenAllowanceCaveats()`
19+
- `createTokenApprovalRevocationCaveats()`
1220
- Add `makePermissionDecoderConfigs` to resolve the `PermissionDecoderConfig`s to be used with @metamask/gator-permissions-controller ([#259](https://github.com/MetaMask/smart-accounts-kit/pull/259))
1321

1422
## [0.7.1]

packages/7715-permission-types/src/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,26 @@ export type {
1717
RevokeExecutionPermissionRequestParams,
1818
RevokeExecutionPermissionResponseResult,
1919
MetaMaskBasePermissionData,
20+
Populated,
2021
} from './types';
2122

2223
export type { PayeeRule, RedeemerRule, ExpiryRule } from './permissions';
2324
export {
2425
makePermissionDecoderConfigs,
26+
createErc20TokenAllowanceCaveats,
27+
type Erc20TokenAllowanceEnforcers,
28+
createErc20TokenPeriodicCaveats,
29+
type Erc20TokenPeriodicEnforcers,
30+
createErc20TokenStreamCaveats,
31+
type Erc20TokenStreamEnforcers,
32+
createNativeTokenAllowanceCaveats,
33+
type NativeTokenAllowanceEnforcers,
34+
createNativeTokenPeriodicCaveats,
35+
type NativeTokenPeriodicEnforcers,
36+
createNativeTokenStreamCaveats,
37+
type NativeTokenStreamEnforcers,
38+
createTokenApprovalRevocationCaveats,
39+
type TokenApprovalRevocationEnforcers,
2540
type DeployedContractsByName,
2641
type PermissionDecoderConfig,
2742
} from './permissions';

packages/7715-permission-types/src/permissions/caveats/erc20TokenAllowance.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
import { hexToNumber } from '@metamask/utils';
1+
import type { Caveat } from '@metamask/delegation-core';
2+
import {
3+
createERC20TokenPeriodTransferTerms,
4+
createValueLteTerms,
5+
} from '@metamask/delegation-core';
6+
import { hexToBigInt, hexToNumber } from '@metamask/utils';
27

3-
import type { Erc20TokenAllowancePermission } from '../../types';
8+
import type { Erc20TokenAllowancePermission, Populated } from '../../types';
49
import { expiryRuleDecoder } from '../rules/expiry';
510
import { erc20PayeeRuleDecoder } from '../rules/payee';
611
import { redeemerRuleDecoder } from '../rules/redeemer';
712
import type {
813
ChecksumCaveat,
914
ChecksumEnforcersByChainId,
1015
DecodedPermissionData,
11-
MakePermissionDecoderConfig,
16+
PermissionDecoderConfig,
1217
} from '../types';
1318
import {
1419
getByteLength,
@@ -26,7 +31,7 @@ import {
2631
*/
2732
export function makeErc20TokenAllowanceDecoderConfig(
2833
contractAddresses: ChecksumEnforcersByChainId,
29-
): MakePermissionDecoderConfig {
34+
): PermissionDecoderConfig {
3035
const {
3136
timestampEnforcer,
3237
erc20PeriodicEnforcer,
@@ -111,3 +116,62 @@ function validateAndDecodeData(
111116

112117
return { tokenAddress, allowanceAmount, startTime };
113118
}
119+
120+
/**
121+
* Enforcers required to build ERC-20 token allowance caveats.
122+
*/
123+
export type Erc20TokenAllowanceEnforcers = Pick<
124+
ChecksumEnforcersByChainId,
125+
'erc20PeriodicEnforcer' | 'valueLteEnforcer'
126+
>;
127+
128+
/**
129+
* Builds the erc20-token-allowance caveats required for this permission type.
130+
*
131+
* @param options0 - Caveat builder arguments.
132+
* @param options0.permission - Fully populated erc20-token-allowance permission data.
133+
* @param options0.contracts - Enforcer addresses used to construct caveats.
134+
* @returns The ERC-20 allowance and zero-value caveats.
135+
*/
136+
export function createErc20TokenAllowanceCaveats({
137+
permission,
138+
contracts,
139+
}: {
140+
permission: Populated<Erc20TokenAllowancePermission>;
141+
contracts: Erc20TokenAllowanceEnforcers;
142+
}): Caveat[] {
143+
const { tokenAddress, allowanceAmount, startTime } = permission.data;
144+
const allowanceAmountBigInt = hexToBigInt(allowanceAmount);
145+
146+
if (allowanceAmountBigInt === 0n) {
147+
throw new Error(
148+
'Invalid erc20-token-allowance permission: allowanceAmount must be a positive number.',
149+
);
150+
}
151+
152+
if (startTime <= 0) {
153+
throw new Error(
154+
'Invalid erc20-token-allowance permission: startTime must be a positive number.',
155+
);
156+
}
157+
158+
const erc20PeriodCaveat: Caveat = {
159+
enforcer: contracts.erc20PeriodicEnforcer,
160+
terms: createERC20TokenPeriodTransferTerms({
161+
tokenAddress,
162+
periodAmount: allowanceAmountBigInt,
163+
// delegation-core accepts bigint for encoding although the type is `number`.
164+
periodDuration: BigInt(UINT256_MAX) as unknown as number,
165+
startDate: startTime,
166+
}),
167+
args: '0x',
168+
};
169+
170+
const valueLteCaveat: Caveat = {
171+
enforcer: contracts.valueLteEnforcer,
172+
terms: createValueLteTerms({ maxValue: 0n }),
173+
args: '0x',
174+
};
175+
176+
return [erc20PeriodCaveat, valueLteCaveat];
177+
}

packages/7715-permission-types/src/permissions/caveats/erc20TokenPeriodic.ts

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
import { decodeERC20TokenPeriodTransferTerms } from '@metamask/delegation-core';
2-
import { bigIntToHex } from '@metamask/utils';
1+
import type { Caveat } from '@metamask/delegation-core';
2+
import {
3+
createERC20TokenPeriodTransferTerms,
4+
createValueLteTerms,
5+
decodeERC20TokenPeriodTransferTerms,
6+
} from '@metamask/delegation-core';
7+
import { bigIntToHex, hexToBigInt } from '@metamask/utils';
38

4-
import type { Erc20TokenPeriodicPermission } from '../../types';
9+
import type { Erc20TokenPeriodicPermission, Populated } from '../../types';
510
import { expiryRuleDecoder } from '../rules/expiry';
611
import { erc20PayeeRuleDecoder } from '../rules/payee';
712
import { redeemerRuleDecoder } from '../rules/redeemer';
813
import type {
914
ChecksumCaveat,
1015
ChecksumEnforcersByChainId,
1116
DecodedPermissionData,
12-
MakePermissionDecoderConfig,
17+
PermissionDecoderConfig,
1318
} from '../types';
1419
import {
1520
getTermsByEnforcer,
@@ -25,7 +30,7 @@ import {
2530
*/
2631
export function makeErc20TokenPeriodicDecoderConfig(
2732
contractAddresses: ChecksumEnforcersByChainId,
28-
): MakePermissionDecoderConfig {
33+
): PermissionDecoderConfig {
2934
const {
3035
timestampEnforcer,
3136
erc20PeriodicEnforcer,
@@ -117,3 +122,74 @@ function validateAndDecodeData(
117122
startTime,
118123
};
119124
}
125+
126+
/**
127+
* Enforcers required to build ERC-20 token periodic caveats.
128+
*/
129+
export type Erc20TokenPeriodicEnforcers = Pick<
130+
ChecksumEnforcersByChainId,
131+
'erc20PeriodicEnforcer' | 'valueLteEnforcer'
132+
>;
133+
134+
/**
135+
* Builds the erc20-token-periodic caveats required for this permission type.
136+
*
137+
* @param options0 - Caveat builder arguments.
138+
* @param options0.permission - Fully populated erc20-token-periodic permission data.
139+
* @param options0.contracts - Enforcer addresses used to construct caveats.
140+
* @returns The ERC-20 periodic and zero-value caveats.
141+
*/
142+
export function createErc20TokenPeriodicCaveats({
143+
permission,
144+
contracts,
145+
}: {
146+
permission: Populated<Erc20TokenPeriodicPermission>;
147+
contracts: Erc20TokenPeriodicEnforcers;
148+
}): Caveat[] {
149+
const { tokenAddress, periodAmount, periodDuration, startTime } =
150+
permission.data;
151+
const periodAmountBigInt = hexToBigInt(periodAmount);
152+
153+
if (periodAmountBigInt === 0n) {
154+
throw new Error(
155+
'Invalid erc20-token-periodic permission: periodAmount must be a positive number.',
156+
);
157+
}
158+
159+
if (periodDuration <= 0) {
160+
throw new Error(
161+
'Invalid erc20-token-periodic permission: periodDuration must be a positive number.',
162+
);
163+
}
164+
165+
if (periodDuration > MAX_PERIOD_DURATION) {
166+
throw new Error(
167+
'Invalid erc20-token-periodic permission: periodDuration must be less than or equal to MAX_PERIOD_DURATION.',
168+
);
169+
}
170+
171+
if (startTime <= 0) {
172+
throw new Error(
173+
'Invalid erc20-token-periodic permission: startTime must be a positive number.',
174+
);
175+
}
176+
177+
const erc20PeriodCaveat: Caveat = {
178+
enforcer: contracts.erc20PeriodicEnforcer,
179+
terms: createERC20TokenPeriodTransferTerms({
180+
tokenAddress,
181+
periodAmount: periodAmountBigInt,
182+
periodDuration,
183+
startDate: startTime,
184+
}),
185+
args: '0x',
186+
};
187+
188+
const valueLteCaveat: Caveat = {
189+
enforcer: contracts.valueLteEnforcer,
190+
terms: createValueLteTerms({ maxValue: 0n }),
191+
args: '0x',
192+
};
193+
194+
return [erc20PeriodCaveat, valueLteCaveat];
195+
}

packages/7715-permission-types/src/permissions/caveats/erc20TokenStream.ts

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
import { decodeERC20StreamingTerms } from '@metamask/delegation-core';
2-
import { bigIntToHex } from '@metamask/utils';
1+
import type { Caveat } from '@metamask/delegation-core';
2+
import {
3+
createERC20StreamingTerms,
4+
createValueLteTerms,
5+
decodeERC20StreamingTerms,
6+
} from '@metamask/delegation-core';
7+
import { bigIntToHex, hexToBigInt } from '@metamask/utils';
38

4-
import type { Erc20TokenStreamPermission } from '../../types';
9+
import type { Erc20TokenStreamPermission, Populated } from '../../types';
510
import { expiryRuleDecoder } from '../rules/expiry';
611
import { erc20PayeeRuleDecoder } from '../rules/payee';
712
import { redeemerRuleDecoder } from '../rules/redeemer';
813
import type {
914
ChecksumCaveat,
10-
ChecksumEnforcersByChainId,
1115
DecodedPermissionData,
12-
MakePermissionDecoderConfig,
16+
ChecksumEnforcersByChainId,
17+
PermissionDecoderConfig,
1318
} from '../types';
1419
import { getTermsByEnforcer, ZERO_32_BYTES } from '../utils';
1520

@@ -21,7 +26,7 @@ import { getTermsByEnforcer, ZERO_32_BYTES } from '../utils';
2126
*/
2227
export function makeErc20TokenStreamDecoderConfig(
2328
contractAddresses: ChecksumEnforcersByChainId,
24-
): MakePermissionDecoderConfig {
29+
): PermissionDecoderConfig {
2530
const {
2631
timestampEnforcer,
2732
erc20StreamingEnforcer,
@@ -104,3 +109,75 @@ function validateAndDecodeData(
104109
startTime,
105110
};
106111
}
112+
113+
/**
114+
* Enforcers required to build ERC-20 token stream caveats.
115+
*/
116+
export type Erc20TokenStreamEnforcers = Pick<
117+
ChecksumEnforcersByChainId,
118+
'erc20StreamingEnforcer' | 'valueLteEnforcer'
119+
>;
120+
121+
/**
122+
* Builds the ERC-20 stream caveats required for this permission type.
123+
*
124+
* @param options0 - Caveat builder arguments.
125+
* @param options0.permission - Fully populated ERC-20 stream permission data.
126+
* @param options0.contracts - Enforcer addresses used to construct caveats.
127+
* @returns The ERC20 streaming and zero-value caveats.
128+
*/
129+
export function createErc20TokenStreamCaveats({
130+
permission,
131+
contracts,
132+
}: {
133+
permission: Populated<Erc20TokenStreamPermission>;
134+
contracts: Erc20TokenStreamEnforcers;
135+
}): Caveat[] {
136+
const { initialAmount, maxAmount, amountPerSecond, startTime } =
137+
permission.data;
138+
const initialAmountBigInt = hexToBigInt(initialAmount);
139+
const maxAmountBigInt = hexToBigInt(maxAmount);
140+
const amountPerSecondBigInt = hexToBigInt(amountPerSecond);
141+
142+
if (maxAmountBigInt <= initialAmountBigInt) {
143+
throw new Error(
144+
'Invalid erc20-token-stream permission: maxAmount must be greater than initialAmount.',
145+
);
146+
}
147+
148+
if (amountPerSecondBigInt === 0n) {
149+
throw new Error(
150+
'Invalid erc20-token-stream permission: amountPerSecond must be a positive number.',
151+
);
152+
}
153+
154+
if (startTime <= 0) {
155+
throw new Error(
156+
'Invalid erc20-token-stream permission: startTime must be a positive number.',
157+
);
158+
}
159+
160+
// ERC20StreamingEnforcer: enforce token address, initial/max amount, amount per second, and start time.
161+
const erc20StreamingCaveat: Caveat = {
162+
enforcer: contracts.erc20StreamingEnforcer,
163+
terms: createERC20StreamingTerms({
164+
tokenAddress: permission.data.tokenAddress,
165+
initialAmount: initialAmountBigInt,
166+
maxAmount: maxAmountBigInt,
167+
amountPerSecond: amountPerSecondBigInt,
168+
startTime,
169+
}),
170+
args: '0x',
171+
};
172+
173+
// ValueLteEnforcer: allow no native value (e.g. msg.value must be 0).
174+
const valueLteCaveat: Caveat = {
175+
enforcer: contracts.valueLteEnforcer,
176+
terms: createValueLteTerms({
177+
maxValue: 0n,
178+
}),
179+
args: '0x',
180+
};
181+
182+
return [erc20StreamingCaveat, valueLteCaveat];
183+
}

0 commit comments

Comments
 (0)