Skip to content

Commit 9cdc7db

Browse files
committed
feat: added contract utils
1 parent 9c62033 commit 9cdc7db

26 files changed

Lines changed: 4010 additions & 4 deletions

File tree

packages/delegation-core/test/caveats/nativeTokenPeriodTransfer.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isStrictHexString } from '@metamask/utils';
12
import { describe, it, expect } from 'vitest';
23

34
import { createNativeTokenPeriodTransferTerms } from '../../src/caveats/nativeTokenPeriodTransfer';
@@ -53,7 +54,8 @@ describe('createNativeTokenPeriodTransferTerms', () => {
5354
});
5455

5556
expect(result).toHaveLength(194);
56-
expect(result).toMatch(/^0x[0-9a-f]{192}$/u);
57+
expect(isStrictHexString(result)).toBe(true);
58+
expect(result.length).toBe(194); // Additional length validation
5759
});
5860

5961
it('creates valid terms for maximum safe values', () => {
@@ -68,7 +70,8 @@ describe('createNativeTokenPeriodTransferTerms', () => {
6870
});
6971

7072
expect(result).toHaveLength(194);
71-
expect(result).toMatch(/^0x[0-9a-f]{192}$/u);
73+
expect(isStrictHexString(result)).toBe(true);
74+
expect(result.length).toBe(194); // Additional length validation
7275
});
7376

7477
it('throws an error for zero period amount', () => {
@@ -225,8 +228,9 @@ describe('createNativeTokenPeriodTransferTerms', () => {
225228
startDate,
226229
});
227230

228-
expect(result).toMatch(/^0x[0-9a-f]{192}$/u);
231+
expect(isStrictHexString(result)).toBe(true);
229232
expect(result).toHaveLength(194);
233+
expect(result.length).toBe(194); // Additional length validation
230234
});
231235

232236
// Tests for bytes return type
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { DelegationManager } from '@metamask/delegation-abis';
2+
import type { Address, Client, Hex } from 'viem';
3+
import { readContract } from 'viem/actions';
4+
5+
export type ReadDisabledDelegationsParameters = {
6+
client: Client;
7+
contractAddress: Address;
8+
delegationHash: Hex;
9+
};
10+
11+
export const read = async ({
12+
client,
13+
contractAddress,
14+
delegationHash,
15+
}: ReadDisabledDelegationsParameters) =>
16+
await readContract(client, {
17+
address: contractAddress,
18+
abi: DelegationManager.abi,
19+
functionName: 'disabledDelegations',
20+
args: [delegationHash],
21+
});
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1+
import { read as disabledDelegations } from './methods/disabledDelegations';
12
import { read as getAnyDelegate } from './methods/getAnyDelegate';
23
import { read as getRootAuthority } from './methods/getRootAuthority';
34

4-
export { getAnyDelegate, getRootAuthority };
5+
export { getAnyDelegate, getRootAuthority, disabledDelegations };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as read from './read';
2+
3+
export { read };
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ERC20PeriodTransferEnforcer } from '@metamask/delegation-abis';
2+
import type { Address, Client, Hex } from 'viem';
3+
import { readContract } from 'viem/actions';
4+
5+
export type ReadGetAvailableAmountParameters = {
6+
client: Client;
7+
contractAddress: Address;
8+
delegationHash: Hex;
9+
delegationManager: Address;
10+
terms: Hex;
11+
};
12+
13+
export const read = async ({
14+
client,
15+
contractAddress,
16+
delegationHash,
17+
delegationManager,
18+
terms,
19+
}: ReadGetAvailableAmountParameters) => {
20+
const [availableAmount, isNewPeriod, currentPeriod] = await readContract(
21+
client,
22+
{
23+
address: contractAddress,
24+
abi: ERC20PeriodTransferEnforcer.abi,
25+
functionName: 'getAvailableAmount',
26+
args: [delegationHash, delegationManager, terms],
27+
},
28+
);
29+
30+
return {
31+
availableAmount,
32+
isNewPeriod,
33+
currentPeriod,
34+
};
35+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { read as getAvailableAmount } from './methods/getAvailableAmount';
2+
3+
export { getAvailableAmount };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as read from './read';
2+
3+
export { read };
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { ERC20StreamingEnforcer } from '@metamask/delegation-abis';
2+
import type { Address, Client, Hex } from 'viem';
3+
import { readContract, getBlock } from 'viem/actions';
4+
5+
export type ReadGetAvailableAmountParameters = {
6+
client: Client;
7+
contractAddress: Address;
8+
delegationManager: Address;
9+
delegationHash: Hex;
10+
terms: Hex;
11+
};
12+
13+
export const read = async ({
14+
client,
15+
contractAddress,
16+
delegationManager,
17+
delegationHash,
18+
terms,
19+
}: ReadGetAvailableAmountParameters) => {
20+
// Get current block timestamp from blockchain
21+
const currentBlock = await getBlock(client);
22+
const currentTimestamp = currentBlock.timestamp;
23+
24+
// First, get the current state from the contract
25+
const allowanceState = await readContract(client, {
26+
address: contractAddress,
27+
abi: ERC20StreamingEnforcer.abi,
28+
functionName: 'streamingAllowances',
29+
args: [delegationManager, delegationHash],
30+
});
31+
32+
const [initialAmount, maxAmount, amountPerSecond, startTime, spent] =
33+
allowanceState;
34+
35+
// Check if state exists (startTime != 0)
36+
if (startTime !== 0n) {
37+
// State exists, calculate available amount using the stored state
38+
const availableAmount = getAvailableAmount({
39+
initialAmount,
40+
maxAmount,
41+
amountPerSecond,
42+
startTime,
43+
spent,
44+
currentTimestamp,
45+
});
46+
47+
return {
48+
availableAmount,
49+
};
50+
}
51+
52+
// State doesn't exist, decode terms and simulate with spent = 0
53+
const decodedTerms = await readContract(client, {
54+
address: contractAddress,
55+
abi: ERC20StreamingEnforcer.abi,
56+
functionName: 'getTermsInfo',
57+
args: [terms],
58+
});
59+
60+
const [
61+
,
62+
decodedInitialAmount,
63+
decodedMaxAmount,
64+
decodedAmountPerSecond,
65+
decodedStartTime,
66+
] = decodedTerms;
67+
68+
// Simulate using decoded terms with spent = 0
69+
const availableAmount = getAvailableAmount({
70+
initialAmount: decodedInitialAmount,
71+
maxAmount: decodedMaxAmount,
72+
amountPerSecond: decodedAmountPerSecond,
73+
startTime: decodedStartTime,
74+
spent: 0n,
75+
currentTimestamp,
76+
});
77+
78+
return {
79+
availableAmount,
80+
};
81+
};
82+
83+
/**
84+
* Replicates the internal _getAvailableAmount logic from the smart contract.
85+
*
86+
* @param allowance - The allowance object containing all parameters.
87+
* @param allowance.initialAmount - The initial amount available.
88+
* @param allowance.maxAmount - The maximum amount allowed.
89+
* @param allowance.amountPerSecond - The amount streamed per second.
90+
* @param allowance.startTime - The start time of the streaming.
91+
* @param allowance.spent - The amount already spent.
92+
* @param allowance.currentTimestamp - The current timestamp.
93+
* @returns The available amount that can be spent.
94+
*/
95+
function getAvailableAmount(allowance: {
96+
initialAmount: bigint;
97+
maxAmount: bigint;
98+
amountPerSecond: bigint;
99+
startTime: bigint;
100+
spent: bigint;
101+
currentTimestamp: bigint;
102+
}): bigint {
103+
// If current time is before start time, nothing is available
104+
if (allowance.currentTimestamp < allowance.startTime) {
105+
return 0n;
106+
}
107+
108+
// Calculate elapsed time since start
109+
const elapsed = allowance.currentTimestamp - allowance.startTime;
110+
111+
// Calculate total unlocked amount
112+
let unlocked = allowance.initialAmount + allowance.amountPerSecond * elapsed;
113+
114+
// Cap by max amount
115+
if (unlocked > allowance.maxAmount) {
116+
unlocked = allowance.maxAmount;
117+
}
118+
119+
// If spent >= unlocked, nothing available
120+
if (allowance.spent >= unlocked) {
121+
return 0n;
122+
}
123+
124+
// Return available amount
125+
return unlocked - allowance.spent;
126+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { read as getAvailableAmount } from './methods/getAvailableAmount';
2+
3+
export { getAvailableAmount };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as read from './read';
2+
3+
export { read };

0 commit comments

Comments
 (0)