Skip to content

Commit 271db59

Browse files
committed
Implement caveat building logic for erc20tokenstream permission type
1 parent f059043 commit 271db59

5 files changed

Lines changed: 148 additions & 7 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export type {
2222
export type { PayeeRule, RedeemerRule, ExpiryRule } from './permissions';
2323
export {
2424
makePermissionDecoderConfigs,
25+
createErc20TokenStreamCaveats,
26+
type Erc20TokenStreamEnforcers,
27+
type Erc20TokenStreamDecoderEnforcers,
2528
type DeployedContractsByName,
2629
type PermissionDecoderConfig,
2730
} from './permissions';

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

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

49
import type { Erc20TokenStreamPermission } from '../../types';
@@ -7,9 +12,10 @@ import { erc20PayeeRuleDecoder } from '../rules/payee';
712
import { redeemerRuleDecoder } from '../rules/redeemer';
813
import type {
914
ChecksumCaveat,
10-
ChecksumEnforcersByChainId,
1115
DecodedPermissionData,
12-
MakePermissionDecoderConfig,
16+
DeepRequired,
17+
ChecksumEnforcersByChainId,
18+
PermissionDecoderConfig,
1319
} from '../types';
1420
import { getTermsByEnforcer, ZERO_32_BYTES } from '../utils';
1521

@@ -21,7 +27,7 @@ import { getTermsByEnforcer, ZERO_32_BYTES } from '../utils';
2127
*/
2228
export function makeErc20TokenStreamDecoderConfig(
2329
contractAddresses: ChecksumEnforcersByChainId,
24-
): MakePermissionDecoderConfig {
30+
): PermissionDecoderConfig {
2531
const {
2632
timestampEnforcer,
2733
erc20StreamingEnforcer,
@@ -104,3 +110,54 @@ function validateAndDecodeData(
104110
startTime,
105111
};
106112
}
113+
114+
/**
115+
* Enforcers required to build ERC-20 token stream caveats.
116+
*/
117+
export type Erc20TokenStreamEnforcers = Pick<
118+
ChecksumEnforcersByChainId,
119+
'erc20StreamingEnforcer' | 'valueLteEnforcer'
120+
>;
121+
122+
/**
123+
* Builds the ERC-20 stream caveats required for this permission type.
124+
*
125+
* @param options0 - Caveat builder arguments.
126+
* @param options0.permission - Fully populated ERC-20 stream permission data.
127+
* @param options0.contracts - Enforcer addresses used to construct caveats.
128+
* @returns The ERC20 streaming and zero-value caveats.
129+
*/
130+
export async function createErc20TokenStreamCaveats({
131+
permission,
132+
contracts,
133+
}: {
134+
permission: DeepRequired<Erc20TokenStreamPermission>;
135+
contracts: Erc20TokenStreamEnforcers;
136+
}): Promise<Caveat[]> {
137+
const { initialAmount, maxAmount, amountPerSecond, startTime } =
138+
permission.data;
139+
140+
// ERC20StreamingEnforcer: enforce token address, initial/max amount, amount per second, and start time.
141+
const erc20StreamingCaveat: Caveat = {
142+
enforcer: contracts.erc20StreamingEnforcer,
143+
terms: createERC20StreamingTerms({
144+
tokenAddress: permission.data.tokenAddress,
145+
initialAmount: BigInt(initialAmount),
146+
maxAmount: BigInt(maxAmount),
147+
amountPerSecond: BigInt(amountPerSecond),
148+
startTime,
149+
}),
150+
args: '0x',
151+
};
152+
153+
// ValueLteEnforcer: allow no native value (e.g. msg.value must be 0).
154+
const valueLteCaveat: Caveat = {
155+
enforcer: contracts.valueLteEnforcer,
156+
terms: createValueLteTerms({
157+
maxValue: 0n,
158+
}),
159+
args: '0x',
160+
};
161+
162+
return [erc20StreamingCaveat, valueLteCaveat];
163+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { makeTokenApprovalRevocationDecoderConfig } from './caveats/tokenApprova
88
import type { DeployedContractsByName, PermissionDecoderConfig } from './types';
99
import { getChecksumEnforcersByChainId } from './utils';
1010

11+
export {
12+
createErc20TokenStreamCaveats,
13+
type Erc20TokenStreamEnforcers,
14+
type Erc20TokenStreamDecoderEnforcers,
15+
} from './caveats/erc20TokenStream';
16+
1117
export type { ExpiryRule } from './rules/expiry';
1218
export type { PayeeRule } from './rules/payee';
1319
export type { RedeemerRule } from './rules/redeemer';

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,18 @@ export type PermissionDecoder = {
9595
* A map of deployed contract names to addresses for one chain.
9696
*/
9797
export type DeployedContractsByName = Record<string, Hex>;
98+
99+
/**
100+
* Makes all properties in an object type required recursively.
101+
* This includes nested objects and arrays.
102+
* Also removes undefined from union types.
103+
*/
104+
export type DeepRequired<TParent> = TParent extends (infer U)[]
105+
? DeepRequired<U>[]
106+
: TParent extends object
107+
? {
108+
[P in keyof TParent]-?: DeepRequired<
109+
Exclude<TParent[P], undefined | null>
110+
>;
111+
}
112+
: Exclude<TParent, undefined | null>;

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

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@ import {
55
import { bigIntToHex, type Hex } from '@metamask/utils';
66
import { describe, it, expect } from 'vitest';
77

8-
import { makePermissionDecoderConfigs } from '../../../src/permissions';
9-
import { makeErc20TokenStreamDecoderConfig } from '../../../src/permissions/caveats/erc20TokenStream';
8+
import { makePermissionDecoderConfigs} from '../../../src/permissions';
9+
import {
10+
createErc20TokenStreamCaveats,
11+
type Erc20TokenStreamEnforcers,
12+
makeErc20TokenStreamDecoderConfig,
13+
} from '../../../src/permissions/caveats/erc20TokenStream';
1014
import { expiryRuleDecoder } from '../../../src/permissions/rules/expiry';
1115
import { erc20PayeeRuleDecoder } from '../../../src/permissions/rules/payee';
1216
import { redeemerRuleDecoder } from '../../../src/permissions/rules/redeemer';
13-
import type { ChecksumCaveat } from '../../../src/permissions/types';
17+
import type {
18+
ChecksumCaveat,
19+
DeepRequired,
20+
} from '../../../src/permissions/types';
1421
import {
1522
getChecksumEnforcersByChainId,
1623
ZERO_32_BYTES,
1724
} from '../../../src/permissions/utils';
25+
import type { Erc20TokenStreamPermission } from '../../../src/types';
1826
import { toWord } from '../../test-utils';
1927

2028
describe('erc20-token-stream decoder config', () => {
@@ -173,3 +181,55 @@ describe('erc20-token-stream decoder config', () => {
173181
});
174182
});
175183
});
184+
185+
describe('createErc20TokenStreamCaveats()', () => {
186+
const initialAmount = '0x0de0b6b3a7640000' as const;
187+
const maxAmount = '0x8ac7230489e80000' as const;
188+
const amountPerSecond = '0x06f05b59d3b20000' as const;
189+
const startTime = 1729900800; // 10/26/2024 00:00:00 UTC
190+
const tokenAddress = '0x1234567890123456789012345678901234567890' as const;
191+
192+
const contracts: Erc20TokenStreamEnforcers = {
193+
erc20StreamingEnforcer: '0x7356Ed4321Ff9e7DAE246461829cDC170ff660Ab',
194+
valueLteEnforcer: '0x5e12Ca712176E7557e4fAa1c8cc27382B60B5e39',
195+
};
196+
197+
const mockPermission: DeepRequired<Erc20TokenStreamPermission> = {
198+
type: 'erc20-token-stream',
199+
data: {
200+
initialAmount,
201+
maxAmount,
202+
amountPerSecond,
203+
startTime,
204+
tokenAddress,
205+
justification: 'test',
206+
},
207+
isAdjustmentAllowed: true,
208+
};
209+
210+
it('creates erc20Streaming and valueLte caveats', async () => {
211+
const caveats = await createErc20TokenStreamCaveats({
212+
permission: mockPermission,
213+
contracts,
214+
});
215+
const initialAmountHex = initialAmount.slice(2).padStart(64, '0');
216+
const maxAmountHex = maxAmount.slice(2).padStart(64, '0');
217+
const amountPerSecondHex = amountPerSecond.slice(2).padStart(64, '0');
218+
const startTimeHex = startTime.toString(16).padStart(64, '0');
219+
const erc20StreamingExpectedTerms = `0x${tokenAddress.slice(2)}${initialAmountHex}${maxAmountHex}${amountPerSecondHex}${startTimeHex}`;
220+
221+
expect(caveats).toStrictEqual([
222+
{
223+
enforcer: contracts.erc20StreamingEnforcer,
224+
terms: erc20StreamingExpectedTerms,
225+
args: '0x',
226+
},
227+
{
228+
enforcer: contracts.valueLteEnforcer,
229+
terms:
230+
'0x0000000000000000000000000000000000000000000000000000000000000000',
231+
args: '0x',
232+
},
233+
]);
234+
});
235+
});

0 commit comments

Comments
 (0)