Skip to content

Commit 4d784ae

Browse files
committed
Add explicit tests for rule decoders
1 parent 1f45727 commit 4d784ae

4 files changed

Lines changed: 390 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { createAllowedCalldataTerms } from '@metamask/delegation-core';
2+
import {
3+
CHAIN_ID,
4+
DELEGATOR_CONTRACTS,
5+
} from '@metamask/delegation-deployments';
6+
import { getChecksumAddress } from '@metamask/utils';
7+
import type { Hex } from '@metamask/utils';
8+
9+
import { erc20PayeeRule } from './erc20PayeeRule';
10+
import type { ChecksumCaveat } from '../types';
11+
import { getChecksumEnforcersByChainId } from '../utils';
12+
13+
describe('erc20PayeeRule', () => {
14+
const contracts = DELEGATOR_CONTRACTS['1.3.0'][CHAIN_ID.sepolia];
15+
const contractAddresses = getChecksumEnforcersByChainId(contracts);
16+
const { allowedCalldataEnforcer, nonceEnforcer } = contractAddresses;
17+
const requiredEnforcers = new Map<Hex, number>([[nonceEnforcer, 1]]);
18+
19+
const PAYEE_ADDRESS: Hex = '0x3333333333333333333333333333333333333333';
20+
const paddedPayee: Hex = `0x${PAYEE_ADDRESS.slice(2).padStart(64, '0')}`;
21+
22+
const validPayeeCaveat: ChecksumCaveat = {
23+
enforcer: allowedCalldataEnforcer,
24+
terms: createAllowedCalldataTerms({
25+
startIndex: 4,
26+
value: paddedPayee,
27+
}),
28+
args: '0x' as Hex,
29+
};
30+
31+
it('returns null when no AllowedCalldataEnforcer caveat is present', () => {
32+
const caveats: ChecksumCaveat[] = [
33+
{ enforcer: nonceEnforcer, terms: '0x' as Hex, args: '0x' as Hex },
34+
];
35+
36+
expect(
37+
erc20PayeeRule({ contractAddresses, caveats, requiredEnforcers }),
38+
).toBeNull();
39+
});
40+
41+
it('returns a payee rule with the decoded checksummed address', () => {
42+
expect(
43+
erc20PayeeRule({
44+
contractAddresses,
45+
caveats: [validPayeeCaveat],
46+
requiredEnforcers,
47+
}),
48+
).toStrictEqual({
49+
type: 'payee',
50+
data: { addresses: [getChecksumAddress(PAYEE_ADDRESS)] },
51+
});
52+
});
53+
54+
it('throws when allowedCalldataEnforcer is configured as required', () => {
55+
const requiredWithPayee = new Map<Hex, number>([
56+
[nonceEnforcer, 1],
57+
[allowedCalldataEnforcer, 1],
58+
]);
59+
60+
expect(() =>
61+
erc20PayeeRule({
62+
contractAddresses,
63+
caveats: [validPayeeCaveat],
64+
requiredEnforcers: requiredWithPayee,
65+
}),
66+
).toThrow(
67+
'Invalid payee caveats: payee enforcer may not be a required caveat',
68+
);
69+
});
70+
71+
it('throws when more than one AllowedCalldataEnforcer caveat is present', () => {
72+
expect(() =>
73+
erc20PayeeRule({
74+
contractAddresses,
75+
caveats: [validPayeeCaveat, validPayeeCaveat],
76+
requiredEnforcers,
77+
}),
78+
).toThrow(
79+
'Invalid payee caveats: multiple AllowedCalldataEnforcer caveats',
80+
);
81+
});
82+
83+
it('throws when startIndex is not 4', () => {
84+
const caveat: ChecksumCaveat = {
85+
enforcer: allowedCalldataEnforcer,
86+
terms: createAllowedCalldataTerms({
87+
startIndex: 0,
88+
value: paddedPayee,
89+
}),
90+
args: '0x' as Hex,
91+
};
92+
93+
expect(() =>
94+
erc20PayeeRule({
95+
contractAddresses,
96+
caveats: [caveat],
97+
requiredEnforcers,
98+
}),
99+
).toThrow(
100+
'Invalid payee caveat: AllowedCalldataEnforcer startIndex must be 4',
101+
);
102+
});
103+
104+
it('throws when the encoded value is not 32 bytes long', () => {
105+
const shortValue: Hex = `0x${PAYEE_ADDRESS.slice(2)}`;
106+
const caveat: ChecksumCaveat = {
107+
enforcer: allowedCalldataEnforcer,
108+
terms: createAllowedCalldataTerms({
109+
startIndex: 4,
110+
value: shortValue,
111+
}),
112+
args: '0x' as Hex,
113+
};
114+
115+
expect(() =>
116+
erc20PayeeRule({
117+
contractAddresses,
118+
caveats: [caveat],
119+
requiredEnforcers,
120+
}),
121+
).toThrow(
122+
'Invalid payee caveat: AllowedCalldataEnforcer value must be 32 bytes long',
123+
);
124+
});
125+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { createTimestampTerms } from '@metamask/delegation-core';
2+
import {
3+
CHAIN_ID,
4+
DELEGATOR_CONTRACTS,
5+
} from '@metamask/delegation-deployments';
6+
import type { Hex } from '@metamask/utils';
7+
8+
import { expiryRule } from './expiryRule';
9+
import type { ChecksumCaveat } from '../types';
10+
import { getChecksumEnforcersByChainId } from '../utils';
11+
12+
describe('expiryRule', () => {
13+
const contracts = DELEGATOR_CONTRACTS['1.3.0'][CHAIN_ID.sepolia];
14+
const contractAddresses = getChecksumEnforcersByChainId(contracts);
15+
const { timestampEnforcer, nonceEnforcer } = contractAddresses;
16+
const requiredEnforcers = new Map<Hex, number>([[nonceEnforcer, 1]]);
17+
18+
it('returns null when no TimestampEnforcer caveat is present', () => {
19+
const caveats: ChecksumCaveat[] = [
20+
{ enforcer: nonceEnforcer, terms: '0x' as Hex, args: '0x' as Hex },
21+
];
22+
23+
expect(
24+
expiryRule({ contractAddresses, caveats, requiredEnforcers }),
25+
).toBeNull();
26+
});
27+
28+
it('returns an expiry rule with the decoded timestamp when TimestampEnforcer is present', () => {
29+
const beforeThreshold = 1_750_000_000;
30+
const caveats: ChecksumCaveat[] = [
31+
{
32+
enforcer: timestampEnforcer,
33+
terms: createTimestampTerms({
34+
afterThreshold: 0,
35+
beforeThreshold,
36+
}),
37+
args: '0x' as Hex,
38+
},
39+
];
40+
41+
expect(
42+
expiryRule({ contractAddresses, caveats, requiredEnforcers }),
43+
).toStrictEqual({
44+
type: 'expiry',
45+
data: { timestamp: beforeThreshold },
46+
});
47+
});
48+
49+
it('ignores caveats from unrelated enforcers', () => {
50+
const beforeThreshold = 1_700_000_000;
51+
const caveats: ChecksumCaveat[] = [
52+
{ enforcer: nonceEnforcer, terms: '0x' as Hex, args: '0x' as Hex },
53+
{
54+
enforcer: timestampEnforcer,
55+
terms: createTimestampTerms({
56+
afterThreshold: 0,
57+
beforeThreshold,
58+
}),
59+
args: '0x' as Hex,
60+
},
61+
];
62+
63+
expect(
64+
expiryRule({ contractAddresses, caveats, requiredEnforcers }),
65+
).toStrictEqual({
66+
type: 'expiry',
67+
data: { timestamp: beforeThreshold },
68+
});
69+
});
70+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { createAllowedTargetsTerms } from '@metamask/delegation-core';
2+
import {
3+
CHAIN_ID,
4+
DELEGATOR_CONTRACTS,
5+
} from '@metamask/delegation-deployments';
6+
import { getChecksumAddress } from '@metamask/utils';
7+
import type { Hex } from '@metamask/utils';
8+
9+
import { nativePayeeRule } from './nativePayeeRule';
10+
import type { ChecksumCaveat } from '../types';
11+
import { getChecksumEnforcersByChainId } from '../utils';
12+
13+
describe('nativePayeeRule', () => {
14+
const contracts = DELEGATOR_CONTRACTS['1.3.0'][CHAIN_ID.sepolia];
15+
const contractAddresses = getChecksumEnforcersByChainId(contracts);
16+
const { allowedTargetsEnforcer, nonceEnforcer } = contractAddresses;
17+
const requiredEnforcers = new Map<Hex, number>([[nonceEnforcer, 1]]);
18+
19+
const PAYEE_A: Hex = '0x4444444444444444444444444444444444444444';
20+
const PAYEE_B: Hex = '0x5555555555555555555555555555555555555555';
21+
22+
it('returns null when no AllowedTargetsEnforcer caveat is present', () => {
23+
const caveats: ChecksumCaveat[] = [
24+
{ enforcer: nonceEnforcer, terms: '0x' as Hex, args: '0x' as Hex },
25+
];
26+
27+
expect(
28+
nativePayeeRule({ contractAddresses, caveats, requiredEnforcers }),
29+
).toBeNull();
30+
});
31+
32+
it('returns a payee rule with a single decoded checksummed address', () => {
33+
const caveats: ChecksumCaveat[] = [
34+
{
35+
enforcer: allowedTargetsEnforcer,
36+
terms: createAllowedTargetsTerms({ targets: [PAYEE_A] }),
37+
args: '0x' as Hex,
38+
},
39+
];
40+
41+
expect(
42+
nativePayeeRule({ contractAddresses, caveats, requiredEnforcers }),
43+
).toStrictEqual({
44+
type: 'payee',
45+
data: { addresses: [getChecksumAddress(PAYEE_A)] },
46+
});
47+
});
48+
49+
it('returns a payee rule with multiple decoded checksummed addresses', () => {
50+
const caveats: ChecksumCaveat[] = [
51+
{
52+
enforcer: allowedTargetsEnforcer,
53+
terms: createAllowedTargetsTerms({ targets: [PAYEE_A, PAYEE_B] }),
54+
args: '0x' as Hex,
55+
},
56+
];
57+
58+
expect(
59+
nativePayeeRule({ contractAddresses, caveats, requiredEnforcers }),
60+
).toStrictEqual({
61+
type: 'payee',
62+
data: {
63+
addresses: [getChecksumAddress(PAYEE_A), getChecksumAddress(PAYEE_B)],
64+
},
65+
});
66+
});
67+
68+
it('throws when allowedTargetsEnforcer is configured as required', () => {
69+
const requiredWithPayee = new Map<Hex, number>([
70+
[nonceEnforcer, 1],
71+
[allowedTargetsEnforcer, 1],
72+
]);
73+
const caveats: ChecksumCaveat[] = [
74+
{
75+
enforcer: allowedTargetsEnforcer,
76+
terms: createAllowedTargetsTerms({ targets: [PAYEE_A] }),
77+
args: '0x' as Hex,
78+
},
79+
];
80+
81+
expect(() =>
82+
nativePayeeRule({
83+
contractAddresses,
84+
caveats,
85+
requiredEnforcers: requiredWithPayee,
86+
}),
87+
).toThrow(
88+
'Invalid payee caveats: payee enforcer may not be a required caveat',
89+
);
90+
});
91+
92+
it('throws when more than one AllowedTargetsEnforcer caveat is present', () => {
93+
const caveats: ChecksumCaveat[] = [
94+
{
95+
enforcer: allowedTargetsEnforcer,
96+
terms: createAllowedTargetsTerms({ targets: [PAYEE_A] }),
97+
args: '0x' as Hex,
98+
},
99+
{
100+
enforcer: allowedTargetsEnforcer,
101+
terms: createAllowedTargetsTerms({ targets: [PAYEE_B] }),
102+
args: '0x' as Hex,
103+
},
104+
];
105+
106+
expect(() =>
107+
nativePayeeRule({ contractAddresses, caveats, requiredEnforcers }),
108+
).toThrow(
109+
'Invalid payee caveats: multiple AllowedTargetsEnforcer caveats',
110+
);
111+
});
112+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { createRedeemerTerms } from '@metamask/delegation-core';
2+
import {
3+
CHAIN_ID,
4+
DELEGATOR_CONTRACTS,
5+
} from '@metamask/delegation-deployments';
6+
import { getChecksumAddress } from '@metamask/utils';
7+
import type { Hex } from '@metamask/utils';
8+
9+
import { redeemerRule } from './redeemerRule';
10+
import type { ChecksumCaveat } from '../types';
11+
import { getChecksumEnforcersByChainId } from '../utils';
12+
13+
describe('redeemerRule', () => {
14+
const contracts = DELEGATOR_CONTRACTS['1.3.0'][CHAIN_ID.sepolia];
15+
const contractAddresses = getChecksumEnforcersByChainId(contracts);
16+
const { redeemerEnforcer, nonceEnforcer } = contractAddresses;
17+
const requiredEnforcers = new Map<Hex, number>([[nonceEnforcer, 1]]);
18+
19+
const ADDRESS_A: Hex = '0x1111111111111111111111111111111111111111';
20+
const ADDRESS_B: Hex = '0x2222222222222222222222222222222222222222';
21+
22+
it('returns null when no RedeemerEnforcer caveat is present', () => {
23+
const caveats: ChecksumCaveat[] = [
24+
{ enforcer: nonceEnforcer, terms: '0x' as Hex, args: '0x' as Hex },
25+
];
26+
27+
expect(
28+
redeemerRule({ contractAddresses, caveats, requiredEnforcers }),
29+
).toBeNull();
30+
});
31+
32+
it('returns a redeemer rule with a single decoded address', () => {
33+
const caveats: ChecksumCaveat[] = [
34+
{
35+
enforcer: redeemerEnforcer,
36+
terms: createRedeemerTerms({ redeemers: [ADDRESS_A] }),
37+
args: '0x' as Hex,
38+
},
39+
];
40+
41+
expect(
42+
redeemerRule({ contractAddresses, caveats, requiredEnforcers }),
43+
).toStrictEqual({
44+
type: 'redeemer',
45+
data: { addresses: [ADDRESS_A] },
46+
});
47+
});
48+
49+
it('returns a redeemer rule with multiple decoded addresses', () => {
50+
const caveats: ChecksumCaveat[] = [
51+
{
52+
enforcer: redeemerEnforcer,
53+
terms: createRedeemerTerms({ redeemers: [ADDRESS_A, ADDRESS_B] }),
54+
args: '0x' as Hex,
55+
},
56+
];
57+
58+
expect(
59+
redeemerRule({ contractAddresses, caveats, requiredEnforcers }),
60+
).toStrictEqual({
61+
type: 'redeemer',
62+
data: { addresses: [ADDRESS_A, ADDRESS_B] },
63+
});
64+
});
65+
66+
it('ignores caveats from unrelated enforcers', () => {
67+
const caveats: ChecksumCaveat[] = [
68+
{ enforcer: nonceEnforcer, terms: '0x' as Hex, args: '0x' as Hex },
69+
{
70+
enforcer: redeemerEnforcer,
71+
terms: createRedeemerTerms({ redeemers: [ADDRESS_A] }),
72+
args: '0x' as Hex,
73+
},
74+
];
75+
76+
expect(
77+
redeemerRule({ contractAddresses, caveats, requiredEnforcers }),
78+
).toStrictEqual({
79+
type: 'redeemer',
80+
data: { addresses: [getChecksumAddress(ADDRESS_A)] },
81+
});
82+
});
83+
});

0 commit comments

Comments
 (0)