forked from Uniswap/widgets
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathusePermitAllowance.test.ts
147 lines (129 loc) · 5.52 KB
/
usePermitAllowance.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { Contract } from '@ethersproject/contracts'
import { MaxAllowanceTransferAmount, PERMIT2_ADDRESS } from '@uniswap/permit2-sdk'
import { CurrencyAmount } from '@uniswap/sdk-core'
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import PERMIT2_ABI from 'abis/permit2.json'
import { Permit2 } from 'abis/types'
import { SupportedChainId } from 'constants/chains'
import { UNI } from 'constants/tokens'
import { useSingleCallResult } from 'hooks/multicall'
import { useContract } from 'hooks/useContract'
import ms from 'ms.macro'
import { swapEventHandlersAtom } from 'state/swap'
import { renderHook, waitFor } from 'test'
import { usePermitAllowance, useUpdatePermitAllowance } from './usePermitAllowance'
const TOKEN = UNI[SupportedChainId.MAINNET]
const OWNER = hardhat.account.address
const SPENDER = UNIVERSAL_ROUTER_ADDRESS(SupportedChainId.MAINNET)
const CONTRACT = new Contract(PERMIT2_ADDRESS, PERMIT2_ABI) as Permit2
const EXPIRATION = 1234567890
const NONCE = 42
jest.mock('hooks/multicall')
jest.mock('hooks/useContract')
const mockUseContract = useContract as jest.Mock
const mockUseSingleCallResult = useSingleCallResult as jest.Mock
describe('usePermitAllowance', () => {
const FETCH_DEFAULT_FREQ = { blocksPerFetch: undefined }
const FETCH_EVERY_BLOCK = { blocksPerFetch: 1 }
beforeEach(() => mockUseContract.mockReturnValue(CONTRACT))
describe('with allowance not loaded', () => {
beforeEach(() => mockUseSingleCallResult.mockReturnValue({}))
it('fetches allowance', () => {
const { result } = renderHook(() => usePermitAllowance(TOKEN, OWNER, SPENDER))
expect(useContract).toHaveBeenCalledWith(PERMIT2_ADDRESS, expect.anything())
expect(useSingleCallResult).toHaveBeenCalledWith(
CONTRACT,
'allowance',
[OWNER, TOKEN.address, SPENDER],
FETCH_DEFAULT_FREQ
)
expect(result.current).toMatchObject({ permitAllowance: undefined })
})
})
describe('with no allowance', () => {
beforeEach(() =>
mockUseSingleCallResult.mockReturnValue({ result: { amount: BigInt(0), expiration: EXPIRATION, nonce: NONCE } })
)
it('refetches allowance every block - this ensures an allowance is "seen" on the block that it is granted', () => {
const { result } = renderHook(() => usePermitAllowance(TOKEN, OWNER, SPENDER))
expect(useContract).toHaveBeenCalledWith(PERMIT2_ADDRESS, expect.anything())
expect(useSingleCallResult).toHaveBeenCalledWith(
CONTRACT,
'allowance',
[OWNER, TOKEN.address, SPENDER],
FETCH_EVERY_BLOCK
)
expect(result.current).toMatchObject({
permitAllowance: CurrencyAmount.fromRawAmount(TOKEN, 0),
expiration: EXPIRATION,
nonce: NONCE,
})
})
})
describe('with allowance', () => {
beforeEach(() =>
mockUseSingleCallResult.mockReturnValue({
result: { amount: MaxAllowanceTransferAmount.toString(), expiration: EXPIRATION, nonce: NONCE },
})
)
it('fetches allowance', () => {
const { result } = renderHook(() => usePermitAllowance(TOKEN, OWNER, SPENDER))
expect(useSingleCallResult).toHaveBeenCalledWith(
CONTRACT,
'allowance',
[OWNER, TOKEN.address, SPENDER],
FETCH_DEFAULT_FREQ
)
expect(result.current).toMatchObject({
permitAllowance: CurrencyAmount.fromRawAmount(TOKEN, MaxAllowanceTransferAmount.toString()),
expiration: EXPIRATION,
nonce: NONCE,
})
})
})
})
describe('useUpdatePermitAllowance', () => {
it('sends signature request to wallet', async () => {
const onPermitSignature = jest.fn()
const { result } = renderHook(() => useUpdatePermitAllowance(TOKEN, SPENDER, NONCE, onPermitSignature))
expect(result.current).toBeInstanceOf(Function)
const expectedSignature =
'0x1befd08fcc4085dc484346d69fd15659616522454a33e66e7b0f6917379ab888236304ebed307813208bf004da04d998dcd15a8f83241d033e4040adc4b0b5311b'
jest.spyOn(Date, 'now').mockReturnValue(0)
await waitFor(() => result.current())
expect(onPermitSignature).toHaveBeenCalledWith({
details: {
token: TOKEN.address,
amount: MaxAllowanceTransferAmount,
expiration: ms`30d` / 1000,
nonce: NONCE,
},
sigDeadline: ms`30m` / 1000,
signature:
// Signature copied from a signature by the hardhat provider:
expectedSignature,
spender: SPENDER,
})
})
it('triggers onPermit2Allowance', async () => {
const onPermit2Allowance = jest.fn()
const onPermitSignature = jest.fn()
const { result } = renderHook(() => useUpdatePermitAllowance(TOKEN, SPENDER, NONCE, onPermitSignature), {
initialAtomValues: [[swapEventHandlersAtom, { onPermit2Allowance }]],
})
await waitFor(() => result.current())
expect(onPermit2Allowance).toHaveBeenLastCalledWith(
expect.objectContaining({ token: TOKEN, spender: SPENDER }),
expect.any(Promise)
)
await expect(onPermit2Allowance.mock.calls.slice(-1)[0][1]).resolves.toBe(undefined)
})
it('rejects on failure', async () => {
const onPermitSignature = jest.fn()
const { result } = renderHook(() => useUpdatePermitAllowance(TOKEN, SPENDER, NONCE, onPermitSignature))
expect(result.current).toBeInstanceOf(Function)
// Without waiting for hardhat to activate, there will be no chainId; we use this to trigger an error:
await expect(() => result.current()).rejects.toThrow(`${TOKEN.symbol} permit allowance failed: missing chainId`)
expect(onPermitSignature).not.toHaveBeenCalled()
})
})