Skip to content

Commit 1d4d765

Browse files
committed
Add tests for erc20-token-stream, update existing token context to use decimals, and not just assume ether
1 parent b95de92 commit 1d4d765

9 files changed

Lines changed: 526 additions & 33 deletions

File tree

packages/gator-permissions-snap/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snap-7715-permissions.git"
88
},
99
"source": {
10-
"shasum": "Ft70Omb1nOK49mRy2oPSAEBuwiDJsl0Ooig/6O85dCc=",
10+
"shasum": "0TP5ydRCXNvDxWxjTs7Rp1v5C85Ld3tPabxulFWKWHo=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import type { PermissionRequest } from '@metamask/7715-permissions-shared/types'
22
import { extractPermissionName } from '@metamask/7715-permissions-shared/utils';
33

44
import type { AccountController } from '../accountController';
5+
import { erc20TokenStreamPermissionDefinition } from '../permissions/erc20TokenStream';
56
import { nativeTokenPeriodicPermissionDefinition } from '../permissions/nativeTokenPeriodic';
67
import { nativeTokenStreamPermissionDefinition } from '../permissions/nativeTokenStream';
7-
import { erc20TokenStreamPermissionDefinition } from '../permissions/erc20TokenStream';
88
import type { TokenMetadataService } from '../services/tokenMetadataService';
99
import type { TokenPricesService } from '../services/tokenPricesService';
1010
import type { UserEventDispatcher } from '../userEventDispatcher';

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

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { formatEther, maxUint256, parseEther, toHex } from 'viem';
1+
import { formatUnits, maxUint256, parseUnits, toHex } from 'viem';
22

33
import type { AccountController } from '../../accountController';
44
import { TimePeriod } from '../../core/types';
5+
import type { TokenMetadataService } from '../../services/tokenMetadataService';
56
import type { TokenPricesService } from '../../services/tokenPricesService';
67
import { formatUnitsFromString } from '../../utils/balance';
78
import {
@@ -36,18 +37,21 @@ export async function applyContext({
3637
context: Erc20TokenStreamContext;
3738
originalRequest: Erc20TokenStreamPermissionRequest;
3839
}): Promise<Erc20TokenStreamPermissionRequest> {
39-
const { permissionDetails } = context;
40+
const {
41+
permissionDetails,
42+
accountDetails: { decimals },
43+
} = context;
4044
const expiry = convertReadableDateToTimestamp(context.expiry);
4145

4246
const permissionData = {
4347
maxAmount: permissionDetails.maxAmount
44-
? toHex(parseEther(permissionDetails.maxAmount))
48+
? toHex(parseUnits(permissionDetails.maxAmount, decimals))
4549
: undefined,
4650
initialAmount: permissionDetails.initialAmount
47-
? toHex(parseEther(permissionDetails.initialAmount))
51+
? toHex(parseUnits(permissionDetails.initialAmount, decimals))
4852
: undefined,
4953
amountPerSecond: toHex(
50-
parseEther(permissionDetails.amountPerPeriod) /
54+
parseUnits(permissionDetails.amountPerPeriod, decimals) /
5155
TIME_PERIOD_TO_SECONDS[permissionDetails.timePeriod],
5256
),
5357
startTime: convertReadableDateToTimestamp(permissionDetails.startTime),
@@ -95,16 +99,19 @@ export async function populatePermission({
9599
* @param options0.permissionRequest - The Erc20 token stream permission request to convert.
96100
* @param options0.tokenPricesService - Service for fetching token price information.
97101
* @param options0.accountController - Controller for managing account operations.
102+
* @param options0.tokenMetadataService - Service for fetching token metadata.
98103
* @returns A context object containing the formatted permission details and account information.
99104
*/
100105
export async function buildContext({
101106
permissionRequest,
102107
tokenPricesService,
103108
accountController,
109+
tokenMetadataService,
104110
}: {
105111
permissionRequest: Erc20TokenStreamPermissionRequest;
106112
tokenPricesService: TokenPricesService;
107113
accountController: AccountController;
114+
tokenMetadataService: TokenMetadataService;
108115
}): Promise<Erc20TokenStreamContext> {
109116
const chainId = Number(permissionRequest.chainId);
110117
const { tokenAddress } = permissionRequest.permission.data;
@@ -117,8 +124,9 @@ export async function buildContext({
117124
balance: rawBalance,
118125
decimals,
119126
symbol,
120-
} = await accountController.getTokenBalanceAndMetadata({
127+
} = await tokenMetadataService.getTokenBalanceAndMetadata({
121128
chainId,
129+
account: address,
122130
assetAddress: tokenAddress,
123131
});
124132

@@ -153,8 +161,9 @@ export async function buildContext({
153161

154162
// It may seem strange to convert the amount per second to amount per period, format, and then convert back to amount per second.
155163
// The user is inputting amount per period, and we derive amount per second, so it makes sense for the context to contain the amount per period.
156-
const amountPerPeriod = formatEther(
164+
const amountPerPeriod = formatUnits(
157165
amountPerSecond * TIME_PERIOD_TO_SECONDS[timePeriod],
166+
decimals,
158167
);
159168

160169
const startTime = convertTimestampToReadableDate(
@@ -194,7 +203,11 @@ export async function deriveMetadata({
194203
}: {
195204
context: Erc20TokenStreamContext;
196205
}): Promise<Erc20TokenStreamMetadata> {
197-
const { permissionDetails, expiry } = context;
206+
const {
207+
permissionDetails,
208+
expiry,
209+
accountDetails: { decimals },
210+
} = context;
198211

199212
const validationErrors: Erc20TokenStreamMetadata['validationErrors'] = {};
200213

@@ -204,7 +217,7 @@ export async function deriveMetadata({
204217
let amountPerSecond = 'Unknown';
205218
if (permissionDetails.maxAmount) {
206219
try {
207-
maxAmountBigInt = parseEther(permissionDetails.maxAmount);
220+
maxAmountBigInt = parseUnits(permissionDetails.maxAmount, decimals);
208221
if (maxAmountBigInt < 0n) {
209222
validationErrors.maxAmountError = 'Max amount must be greater than 0';
210223
maxAmountBigInt = undefined;
@@ -216,7 +229,10 @@ export async function deriveMetadata({
216229

217230
if (permissionDetails.initialAmount) {
218231
try {
219-
initialAmountBigInt = parseEther(permissionDetails.initialAmount);
232+
initialAmountBigInt = parseUnits(
233+
permissionDetails.initialAmount,
234+
decimals,
235+
);
220236
if (initialAmountBigInt < 0n) {
221237
validationErrors.initialAmountError =
222238
'Initial amount must be greater than 0';
@@ -228,15 +244,19 @@ export async function deriveMetadata({
228244
}
229245

230246
try {
231-
amountPerSecondBigInt = parseEther(permissionDetails.amountPerPeriod);
247+
amountPerSecondBigInt = parseUnits(
248+
permissionDetails.amountPerPeriod,
249+
decimals,
250+
);
232251
if (amountPerSecondBigInt <= 0n) {
233252
validationErrors.amountPerPeriodError =
234253
'Amount per period must be greater than 0';
235254
amountPerSecondBigInt = undefined;
236255
} else {
237-
amountPerSecond = formatEther(
256+
amountPerSecond = formatUnits(
238257
amountPerSecondBigInt /
239258
TIME_PERIOD_TO_SECONDS[permissionDetails.timePeriod],
259+
decimals,
240260
);
241261
}
242262
} catch (error) {

packages/gator-permissions-snap/src/permissions/nativeTokenPeriodic/context.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parseEther, toHex } from 'viem';
1+
import { parseUnits, toHex } from 'viem';
22

33
import type { AccountController } from '../../accountController';
44
import { TimePeriod } from '../../core/types';
@@ -34,11 +34,14 @@ export async function applyContext({
3434
context: NativeTokenPeriodicContext;
3535
originalRequest: NativeTokenPeriodicPermissionRequest;
3636
}): Promise<NativeTokenPeriodicPermissionRequest> {
37-
const { permissionDetails } = context;
37+
const {
38+
permissionDetails,
39+
accountDetails: { decimals },
40+
} = context;
3841
const expiry = convertReadableDateToTimestamp(context.expiry);
3942

4043
const permissionData = {
41-
periodAmount: toHex(parseEther(permissionDetails.periodAmount)),
44+
periodAmount: toHex(parseUnits(permissionDetails.periodAmount, decimals)),
4245
periodDuration: parseInt(permissionDetails.periodDuration, 10),
4346
startTime: convertReadableDateToTimestamp(permissionDetails.startTime),
4447
justification: originalRequest.permission.data.justification,
@@ -175,12 +178,19 @@ export async function deriveMetadata({
175178
}: {
176179
context: NativeTokenPeriodicContext;
177180
}): Promise<NativeTokenPeriodicMetadata> {
178-
const { permissionDetails, expiry } = context;
181+
const {
182+
permissionDetails,
183+
expiry,
184+
accountDetails: { decimals },
185+
} = context;
179186

180187
const validationErrors: NativeTokenPeriodicMetadata['validationErrors'] = {};
181188

182189
try {
183-
const periodAmountBigInt = parseEther(permissionDetails.periodAmount);
190+
const periodAmountBigInt = parseUnits(
191+
permissionDetails.periodAmount,
192+
decimals,
193+
);
184194
if (periodAmountBigInt <= 0n) {
185195
validationErrors.periodAmountError =
186196
'Period amount must be greater than 0';

packages/gator-permissions-snap/src/permissions/nativeTokenStream/context.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { formatEther, maxUint256, parseEther, toHex } from 'viem';
1+
import { formatUnits, maxUint256, parseUnits, toHex } from 'viem';
22

33
import type { AccountController } from '../../accountController';
44
import { TimePeriod } from '../../core/types';
@@ -37,18 +37,21 @@ export async function applyContext({
3737
context: NativeTokenStreamContext;
3838
originalRequest: NativeTokenStreamPermissionRequest;
3939
}): Promise<NativeTokenStreamPermissionRequest> {
40-
const { permissionDetails } = context;
40+
const {
41+
permissionDetails,
42+
accountDetails: { decimals },
43+
} = context;
4144
const expiry = convertReadableDateToTimestamp(context.expiry);
4245

4346
const permissionData = {
4447
maxAmount: permissionDetails.maxAmount
45-
? toHex(parseEther(permissionDetails.maxAmount))
48+
? toHex(parseUnits(permissionDetails.maxAmount, decimals))
4649
: undefined,
4750
initialAmount: permissionDetails.initialAmount
48-
? toHex(parseEther(permissionDetails.initialAmount))
51+
? toHex(parseUnits(permissionDetails.initialAmount, decimals))
4952
: undefined,
5053
amountPerSecond: toHex(
51-
parseEther(permissionDetails.amountPerPeriod) /
54+
parseUnits(permissionDetails.amountPerPeriod, decimals) /
5255
TIME_PERIOD_TO_SECONDS[permissionDetails.timePeriod],
5356
),
5457
startTime: convertReadableDateToTimestamp(permissionDetails.startTime),
@@ -155,8 +158,9 @@ export async function buildContext({
155158

156159
// It may seem strange to convert the amount per second to amount per period, format, and then convert back to amount per second.
157160
// The user is inputting amount per period, and we derive amount per second, so it makes sense for the context to contain the amount per period.
158-
const amountPerPeriod = formatEther(
161+
const amountPerPeriod = formatUnits(
159162
amountPerSecond * TIME_PERIOD_TO_SECONDS[timePeriod],
163+
decimals,
160164
);
161165

162166
const startTime = convertTimestampToReadableDate(
@@ -196,7 +200,11 @@ export async function deriveMetadata({
196200
}: {
197201
context: NativeTokenStreamContext;
198202
}): Promise<NativeTokenStreamMetadata> {
199-
const { permissionDetails, expiry } = context;
203+
const {
204+
permissionDetails,
205+
expiry,
206+
accountDetails: { decimals },
207+
} = context;
200208

201209
const validationErrors: NativeTokenStreamMetadata['validationErrors'] = {};
202210

@@ -206,7 +214,7 @@ export async function deriveMetadata({
206214
let amountPerSecond = 'Unknown';
207215
if (permissionDetails.maxAmount) {
208216
try {
209-
maxAmountBigInt = parseEther(permissionDetails.maxAmount);
217+
maxAmountBigInt = parseUnits(permissionDetails.maxAmount, decimals);
210218
if (maxAmountBigInt < 0n) {
211219
validationErrors.maxAmountError = 'Max amount must be greater than 0';
212220
maxAmountBigInt = undefined;
@@ -218,7 +226,10 @@ export async function deriveMetadata({
218226

219227
if (permissionDetails.initialAmount) {
220228
try {
221-
initialAmountBigInt = parseEther(permissionDetails.initialAmount);
229+
initialAmountBigInt = parseUnits(
230+
permissionDetails.initialAmount,
231+
decimals,
232+
);
222233
if (initialAmountBigInt < 0n) {
223234
validationErrors.initialAmountError =
224235
'Initial amount must be greater than 0';
@@ -230,15 +241,19 @@ export async function deriveMetadata({
230241
}
231242

232243
try {
233-
amountPerSecondBigInt = parseEther(permissionDetails.amountPerPeriod);
244+
amountPerSecondBigInt = parseUnits(
245+
permissionDetails.amountPerPeriod,
246+
decimals,
247+
);
234248
if (amountPerSecondBigInt <= 0n) {
235249
validationErrors.amountPerPeriodError =
236250
'Amount per period must be greater than 0';
237251
amountPerSecondBigInt = undefined;
238252
} else {
239-
amountPerSecond = formatEther(
253+
amountPerSecond = formatUnits(
240254
amountPerSecondBigInt /
241255
TIME_PERIOD_TO_SECONDS[permissionDetails.timePeriod],
256+
decimals,
242257
);
243258
}
244259
} catch (error) {

0 commit comments

Comments
 (0)