|
1 |
| -import { Hex, PublicClient, WalletClient, decodeEventLog, formatEther, parseEther } from 'viem' |
| 1 | +import { Hex } from 'viem' |
2 | 2 | import { assert } from 'chai'
|
3 | 3 | import { EthereumStaker } from '../dist/mjs'
|
4 |
| -import { prepareTests, stake } from './lib/utils' |
5 |
| -import { VaultABI } from '../src/lib/contracts/vaultAbi' |
6 |
| -const amountToStake = parseEther('5') |
7 |
| -const amountToUnstake = parseEther('1') |
8 |
| - |
9 |
| -const originalFetch = global.fetch |
10 |
| - |
11 |
| -// https://github.com/tc39/proposal-promise-with-resolvers/blob/main/polyfills.js |
12 |
| -const withResolvers = <V = unknown, Err = unknown>() => { |
13 |
| - const out: { |
14 |
| - resolve: (value: V) => void |
15 |
| - reject: (reason: Err) => void |
16 |
| - promise: Promise<V> |
17 |
| - } = { |
18 |
| - resolve: () => {}, |
19 |
| - reject: () => {}, |
20 |
| - promise: Promise.resolve() as Promise<V> |
21 |
| - } |
22 |
| - |
23 |
| - out.promise = new Promise<V>((resolve, reject) => { |
24 |
| - out.resolve = resolve |
25 |
| - out.reject = reject |
26 |
| - }) |
27 |
| - |
28 |
| - return out |
29 |
| -} |
30 |
| - |
31 |
| -type VaultEvent = ReturnType<typeof decodeEventLog<typeof VaultABI, 'ExitQueueEntered'>> |
| 4 | +import { prepareTests } from './lib/utils' |
32 | 5 |
|
33 | 6 | describe('EthereumStaker.getUnstakeQueue', () => {
|
34 | 7 | let delegatorAddress: Hex
|
35 | 8 | let validatorAddress: Hex
|
36 |
| - let walletClient: WalletClient |
37 |
| - let publicClient: PublicClient |
38 | 9 | let staker: EthereumStaker
|
39 |
| - let unwatch: () => void = () => {} |
40 |
| - |
41 |
| - const unstake = async (amount: string) => { |
42 |
| - const { tx } = await staker.buildUnstakeTx({ |
43 |
| - delegatorAddress, |
44 |
| - validatorAddress, |
45 |
| - amount |
46 |
| - }) |
47 |
| - |
48 |
| - const request = await walletClient.prepareTransactionRequest({ |
49 |
| - ...tx, |
50 |
| - chain: undefined |
51 |
| - }) |
52 |
| - const hash = await walletClient.sendTransaction({ |
53 |
| - ...request, |
54 |
| - account: delegatorAddress |
55 |
| - }) |
56 |
| - |
57 |
| - const receipt = await publicClient.waitForTransactionReceipt({ hash }) |
58 |
| - assert.equal(receipt.status, 'success') |
59 |
| - } |
60 | 10 |
|
61 | 11 | beforeEach(async () => {
|
62 | 12 | const setup = await prepareTests()
|
63 |
| - |
64 |
| - delegatorAddress = setup.walletClient.account.address |
| 13 | + // Use stale delegator address which never unstaked |
| 14 | + delegatorAddress = '0x15e4287B086f0a8556A5B578a8d8284F19F2c9aC' |
65 | 15 | validatorAddress = setup.validatorAddress
|
66 |
| - publicClient = setup.publicClient |
67 |
| - walletClient = setup.walletClient |
68 | 16 | staker = setup.staker
|
69 |
| - |
70 |
| - await stake({ |
71 |
| - delegatorAddress, |
72 |
| - validatorAddress, |
73 |
| - amountToStake, |
74 |
| - publicClient, |
75 |
| - walletClient, |
76 |
| - staker |
77 |
| - }) |
78 |
| - }) |
79 |
| - |
80 |
| - afterEach(() => { |
81 |
| - unwatch() |
82 |
| - // eslint-disable-next-line @typescript-eslint/ban-ts-comment |
83 |
| - // @ts-ignore |
84 |
| - global.fetch = originalFetch |
85 | 17 | })
|
86 | 18 |
|
87 | 19 | it('should return the unstake queue', async () => {
|
88 |
| - // Subscribe to the ExitQueueEntered events |
89 |
| - const { resolve: eventsResolve, promise: eventsPromise } = withResolvers<VaultEvent[]>() |
90 |
| - const passedEvents: VaultEvent[] = [] |
91 |
| - |
92 |
| - unwatch = publicClient.watchEvent({ |
93 |
| - onLogs: (logs) => { |
94 |
| - const nextEvents = logs |
95 |
| - .map((l) => |
96 |
| - decodeEventLog({ |
97 |
| - abi: VaultABI, |
98 |
| - data: l.data, |
99 |
| - topics: l.topics |
100 |
| - }) |
101 |
| - ) |
102 |
| - .filter((e): e is VaultEvent => e.eventName === 'ExitQueueEntered') |
103 |
| - passedEvents.push(...nextEvents) |
104 |
| - if (passedEvents.length === 2) { |
105 |
| - eventsResolve(passedEvents.sort((a, b) => Number(a.args.shares) - Number(b.args.shares))) |
106 |
| - } |
107 |
| - } |
108 |
| - }) |
109 |
| - |
110 |
| - // Unstake |
111 |
| - |
112 |
| - await unstake(formatEther(amountToUnstake)) |
113 |
| - await unstake('2') |
114 |
| - |
115 |
| - // Wait for the events to be processed |
116 |
| - |
117 |
| - const events = await eventsPromise |
118 |
| - |
119 |
| - assert.strictEqual(events.length, 2) |
120 |
| - // The shares are not exactly the same as the amount to unstake |
121 |
| - assert.closeTo(Number(events[0].args.shares), Number(parseEther('1')), Number(parseEther('0.1'))) |
122 |
| - assert.isTrue(typeof events[0].args.positionTicket === 'bigint') |
123 |
| - |
124 |
| - // mock the request to Stakewise with positionTicket and totalShares from the events |
125 |
| - |
126 |
| - const day = 24 * 60 * 60 |
127 |
| - const mockExitRequests = [ |
128 |
| - { |
129 |
| - positionTicket: events[0].args.positionTicket.toString(), |
130 |
| - totalShares: events[0].args.shares.toString(), |
131 |
| - // earlier |
132 |
| - timestamp: Math.round((new Date().getTime() - 60000) / 1000 - day * 2).toString() |
133 |
| - }, |
134 |
| - { |
135 |
| - positionTicket: events[1].args.positionTicket.toString(), |
136 |
| - totalShares: events[1].args.shares.toString(), |
137 |
| - // later |
138 |
| - timestamp: Math.round(new Date().getTime() / 1000 - day * 2).toString() |
139 |
| - } |
140 |
| - ] |
141 |
| - |
142 |
| - const mockFetch = (input, init) => { |
143 |
| - if (input === 'https://holesky-graph.stakewise.io/subgraphs/name/stakewise/stakewise?opName=exitQueue') { |
144 |
| - return Promise.resolve({ |
145 |
| - ok: true, |
146 |
| - json: () => |
147 |
| - Promise.resolve({ |
148 |
| - data: { |
149 |
| - exitRequests: mockExitRequests |
150 |
| - } |
151 |
| - }) |
152 |
| - }) |
153 |
| - } else { |
154 |
| - return originalFetch(input, init) // Fallback to the original fetch for other URLs |
155 |
| - } |
156 |
| - } |
157 |
| - // eslint-disable-next-line @typescript-eslint/ban-ts-comment |
158 |
| - // @ts-ignore |
159 |
| - global.fetch = mockFetch |
160 |
| - |
161 | 20 | const unstakeQueue = await staker.getUnstakeQueue({
|
162 | 21 | validatorAddress,
|
163 | 22 | delegatorAddress
|
164 | 23 | })
|
165 | 24 |
|
166 |
| - assert.strictEqual(unstakeQueue.length, 2) |
167 |
| - // The queue is sorted by the timestamp from latest to earliest |
168 |
| - const earlierItem = unstakeQueue[1] |
169 |
| - const earlierMock = mockExitRequests[0] |
170 |
| - |
171 |
| - assert.equal(earlierItem.timestamp, new Date(Number(earlierMock.timestamp) * 1000).getTime()) |
172 |
| - // Take into account 1 wei assets conversion issues on the contract |
173 |
| - assert.closeTo(Number(parseEther(earlierItem.totalAmount)), Number(amountToUnstake), 1) |
174 |
| - |
175 |
| - assert.isFalse(earlierItem.isWithdrawable) |
| 25 | + assert.deepEqual(unstakeQueue, [ |
| 26 | + { |
| 27 | + exitQueueIndex: '47', |
| 28 | + positionTicket: '112811942030831899448', |
| 29 | + timestamp: 1711727436000, |
| 30 | + isWithdrawable: true, |
| 31 | + totalAmount: '0.500019229644855834', |
| 32 | + withdrawableAmount: '0' |
| 33 | + } |
| 34 | + ]) |
176 | 35 | })
|
177 | 36 | })
|
0 commit comments