Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import { createWalletGetSupportedExecutionPermissionsHandler } from './wallet-ge
const RESULT_MOCK: GetSupportedExecutionPermissionsResult = {
'native-token-allowance': {
chainIds: ['0x123', '0x345'] as Hex[],
ruleTypes: ['expiry'],
ruleTypes: ['expiry', 'redeemer'],
},
'erc20-token-allowance': {
chainIds: ['0x123'] as Hex[],
ruleTypes: [],
},
'erc721-token-allowance': {
chainIds: ['0x123'] as Hex[],
ruleTypes: ['expiry'],
ruleTypes: ['expiry', 'redeemer'],
},
};

Expand Down
9 changes: 9 additions & 0 deletions packages/gator-permissions-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Support `RedeemerEnforcer` caveat when decoding execution permissions ([#8537](https://github.com/MetaMask/core/pull/8537))
- Permission decoding now recognizes the `RedeemerEnforcer` as an optional caveat on all execution permission types and extracts a `redeemer` rule containing the allowlisted addresses.
- `DecodedPermission` type now includes an optional `rules` property for rules recovered from caveats.
- Export new `EXECUTION_PERMISSION_REDEEMER_RULE_TYPE` constant and `RedeemerRule` type.

### Changed

- Use `decodeRedeemerTerms` from `@metamask/delegation-core` instead of a local implementation ([#8537](https://github.com/MetaMask/core/pull/8537))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is an internal consideration and probably doesn't need to appear in the changelog.

- Bump `@metamask/delegation-core` from `^0.2.0` to `^1.1.0` ([#8537](https://github.com/MetaMask/core/pull/8537))
- Bump `@metamask/transaction-controller` from `^64.2.0` to `^65.0.0` ([#8482](https://github.com/MetaMask/core/pull/8482), [#8585](https://github.com/MetaMask/core/pull/8585), [#8613](https://github.com/MetaMask/core/pull/8613))

## [4.0.0]
Expand Down
2 changes: 1 addition & 1 deletion packages/gator-permissions-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@metamask/7715-permission-types": "^0.5.0",
"@metamask/abi-utils": "^2.0.3",
"@metamask/base-controller": "^9.1.0",
"@metamask/delegation-core": "^0.2.0",
"@metamask/delegation-core": "^1.1.0",
"@metamask/delegation-deployments": "^0.12.0",
"@metamask/messenger": "^1.1.1",
"@metamask/network-controller": "^30.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,9 +835,9 @@ describe('GatorPermissionsController', () => {
const delegator = delegatorAddressA;
const delegate = delegateAddressB;

const timestampBeforeThreshold = 1720000;
const beforeThreshold = 1720000;
const expiryTerms = createTimestampTerms(
{ timestampAfterThreshold: 0, timestampBeforeThreshold },
{ afterThreshold: 0, beforeThreshold },
{ out: 'hex' },
);

Expand Down Expand Up @@ -886,7 +886,7 @@ describe('GatorPermissionsController', () => {
expect(result.from).toBe(delegator);
expect(result.to).toStrictEqual(delegate);
expect(result.permission.type).toBe('native-token-stream');
expect(result.expiry).toBe(timestampBeforeThreshold);
expect(result.expiry).toBe(beforeThreshold);
// amounts are hex-encoded in decoded data; startTime is numeric
expect(result.permission.data.startTime).toBe(startTime);
// BigInt fields are encoded as hex; compare after decoding
Expand Down Expand Up @@ -923,7 +923,7 @@ describe('GatorPermissionsController', () => {
const { TimestampEnforcer, ValueLteEnforcer } = contracts;

const expiryTerms = createTimestampTerms(
{ timestampAfterThreshold: 0, timestampBeforeThreshold: 100 },
{ afterThreshold: 0, beforeThreshold: 100 },
{ out: 'hex' },
);

Expand Down Expand Up @@ -964,7 +964,7 @@ describe('GatorPermissionsController', () => {
} = contracts;

const expiryTerms = createTimestampTerms(
{ timestampAfterThreshold: 0, timestampBeforeThreshold: 1720000 },
{ afterThreshold: 0, beforeThreshold: 1720000 },
{ out: 'hex' },
);

Expand Down Expand Up @@ -1014,9 +1014,9 @@ describe('GatorPermissionsController', () => {
const delegator = delegatorAddressA;
const delegate = delegateAddressB;

const timestampBeforeThreshold = 2000;
const beforeThreshold = 2000;
const expiryTerms = createTimestampTerms(
{ timestampAfterThreshold: 0, timestampBeforeThreshold },
{ afterThreshold: 0, beforeThreshold },
{ out: 'hex' },
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ export class GatorPermissionsController extends BaseController<
});
}

const { expiry, data } = decodeResult;
const { expiry, data, rules } = decodeResult;

const permission = reconstructDecodedPermission({
chainId,
Expand All @@ -620,6 +620,7 @@ export class GatorPermissionsController extends BaseController<
data,
justification,
specifiedOrigin,
rules,
});

return permission;
Expand Down
7 changes: 7 additions & 0 deletions packages/gator-permissions-controller/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@
* contract addresses from `@metamask/delegation-deployments`.
*/
export const DELEGATION_FRAMEWORK_VERSION = '1.3.0';

/**
* `Rule.type` / `wallet_getSupportedExecutionPermissions` `ruleTypes` entry for
* redeemer allowlists (RedeemerEnforcer). Hosts should advertise this for every
* supported execution permission type.
*/
export const EXECUTION_PERMISSION_REDEEMER_RULE_TYPE = 'redeemer' as const;
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('decodePermission', () => {
NativeTokenStreamingEnforcer,
NativeTokenPeriodTransferEnforcer,
NonceEnforcer,
RedeemerEnforcer,
} = contracts;

describe('getPermissionRuleMatchingCaveatTypes()', () => {
Expand Down Expand Up @@ -87,6 +88,35 @@ describe('decodePermission', () => {
expect(result.permissionType).toBe(expectedPermissionType);
});

it('allows RedeemerEnforcer as extra', () => {
const enforcers = [
NativeTokenStreamingEnforcer,
ExactCalldataEnforcer,
NonceEnforcer,
RedeemerEnforcer,
];
const result = findRuleWithMatchingCaveatAddresses({
enforcers,
permissionRules: createPermissionRulesForContracts(contracts),
});
expect(result.permissionType).toBe(expectedPermissionType);
});

it('allows TimestampEnforcer and RedeemerEnforcer as extras', () => {
const enforcers = [
NativeTokenStreamingEnforcer,
ExactCalldataEnforcer,
NonceEnforcer,
TimestampEnforcer,
RedeemerEnforcer,
];
const result = findRuleWithMatchingCaveatAddresses({
enforcers,
permissionRules: createPermissionRulesForContracts(contracts),
});
expect(result.permissionType).toBe(expectedPermissionType);
});

it('rejects forbidden extra caveat', () => {
const enforcers = [
NativeTokenStreamingEnforcer,
Expand Down Expand Up @@ -589,6 +619,39 @@ describe('decodePermission', () => {
expect(result.origin).toBe(specifiedOrigin);
});

it('includes rules when provided', () => {
const permissionType = 'native-token-stream' as const;
const data: DecodedPermission['permission']['data'] = {
initialAmount: '0x01',
maxAmount: '0x02',
amountPerSecond: '0x03',
startTime: 1715664,
} as const;
const rules = [
{
type: 'redeemer' as const,
data: {
addresses: ['0x1111111111111111111111111111111111111111' as Hex],
},
},
];

const result = reconstructDecodedPermission({
chainId,
permissionType,
delegator,
delegate,
authority: ROOT_AUTHORITY,
expiry: null,
data,
justification,
specifiedOrigin,
rules,
});

expect(result.rules).toStrictEqual(rules);
});

it('constructs DecodedPermission with null expiry', () => {
const permissionType = 'erc20-token-periodic' as const;
const data: DecodedPermission['permission']['data'] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const findRuleWithMatchingCaveatAddresses = ({
* @param args.data - Permission-specific decoded data payload.
* @param args.justification - Human-readable justification for the permission.
* @param args.specifiedOrigin - The origin reported in the request metadata.
* @param args.rules - Rules recovered from caveats (e.g. redeemer allowlist).
*
* @returns The reconstructed {@link DecodedPermission}.
*/
Expand All @@ -76,6 +77,7 @@ export const reconstructDecodedPermission = ({
data,
justification,
specifiedOrigin,
rules,
}: {
chainId: number;
permissionType: PermissionType;
Expand All @@ -86,6 +88,7 @@ export const reconstructDecodedPermission = ({
data: DecodedPermission['permission']['data'];
justification: string;
specifiedOrigin: string;
rules?: DecodedPermission['rules'];
}): DecodedPermission => {
if (authority !== ROOT_AUTHORITY) {
throw new Error('Invalid authority');
Expand All @@ -102,6 +105,7 @@ export const reconstructDecodedPermission = ({
},
expiry,
origin: specifiedOrigin,
...(rules === undefined ? {} : { rules }),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no change needed, but I wish there was a nicer way to do this natively in javascript. Maybe at some stage we can create a shared util (somewhere - @metamask/utils???) that we can use in all our codebases.

};

return permission;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ describe('erc20-token-periodic rule', () => {
const expiryCaveat = {
enforcer: TimestampEnforcer,
terms: createTimestampTerms({
timestampAfterThreshold: 0,
timestampBeforeThreshold: 1720000,
afterThreshold: 0,
beforeThreshold: 1720000,
}),
args: '0x' as const,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ export function makeErc20TokenPeriodicRule(
erc20PeriodicEnforcer,
valueLteEnforcer,
nonceEnforcer,
redeemerEnforcer,
} = enforcers;
return makePermissionRule({
permissionType: 'erc20-token-periodic',
optionalEnforcers: [timestampEnforcer],
optionalEnforcers: [timestampEnforcer, redeemerEnforcer],
redeemerEnforcer,
timestampEnforcer,
requiredEnforcers: {
[erc20PeriodicEnforcer]: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe('erc20-token-revocation rule', () => {
const expiryCaveat = {
enforcer: TimestampEnforcer,
terms: createTimestampTerms({
timestampAfterThreshold: 0,
timestampBeforeThreshold: 1720000,
afterThreshold: 0,
beforeThreshold: 1720000,
}),
args: '0x' as const,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ export function makeErc20TokenRevocationRule(
allowedCalldataEnforcer,
valueLteEnforcer,
nonceEnforcer,
redeemerEnforcer,
} = enforcers;
return makePermissionRule({
permissionType: 'erc20-token-revocation',
optionalEnforcers: [timestampEnforcer],
optionalEnforcers: [timestampEnforcer, redeemerEnforcer],
redeemerEnforcer,
timestampEnforcer,
requiredEnforcers: {
[allowedCalldataEnforcer]: 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ describe('erc20-token-stream rule', () => {
const expiryCaveat = {
enforcer: TimestampEnforcer,
terms: createTimestampTerms({
timestampAfterThreshold: 0,
timestampBeforeThreshold: 1720000,
afterThreshold: 0,
beforeThreshold: 1720000,
}),
args: '0x' as const,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ export function makeErc20TokenStreamRule(
erc20StreamingEnforcer,
valueLteEnforcer,
nonceEnforcer,
redeemerEnforcer,
} = enforcers;
return makePermissionRule({
permissionType: 'erc20-token-stream',
optionalEnforcers: [timestampEnforcer],
optionalEnforcers: [timestampEnforcer, redeemerEnforcer],
redeemerEnforcer,
timestampEnforcer,
requiredEnforcers: {
[erc20StreamingEnforcer]: 1,
Expand Down
Loading
Loading