-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
helpers.ts
342 lines (308 loc) · 8.79 KB
/
helpers.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
import { BigNumber, BigNumberish, Contract, ContractTransaction } from 'ethers'
import { providers } from 'ethers'
import { assert, expect } from 'chai'
import hre, { ethers, network } from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import cbor from 'cbor'
import { LinkToken } from '../../typechain'
/**
* Convert string to hex bytes
* @param data string to convert to hex bytes
*/
export function stringToBytes(data: string): string {
return ethers.utils.hexlify(ethers.utils.toUtf8Bytes(data))
}
/**
* Add a hex prefix to a hex string
*
* @param hex The hex string to prepend the hex prefix to
*/
export function addHexPrefix(hex: string): string {
return hex.startsWith('0x') ? hex : `0x${hex}`
}
/**
* Convert a number value to bytes32 format
*
* @param num The number value to convert to bytes32 format
*/
export function numToBytes32(
num: Parameters<typeof ethers.utils.hexlify>[0],
): string {
const hexNum = ethers.utils.hexlify(num)
const strippedNum = stripHexPrefix(hexNum)
if (strippedNum.length > 32 * 2) {
throw Error(
'Cannot convert number to bytes32 format, value is greater than maximum bytes32 value',
)
}
return addHexPrefix(strippedNum.padStart(32 * 2, '0'))
}
/**
* Retrieve single log from transaction
*
* @param tx The transaction to wait for, then extract logs from
* @param index The index of the log to retrieve
*/
export async function getLog(
tx: ContractTransaction,
index: number,
): Promise<providers.Log> {
const logs = await getLogs(tx)
if (!logs[index]) {
throw Error('unable to extract log from transaction receipt')
}
return logs[index]
}
/**
* Extract array of logs from a transaction
*
* @param tx The transaction to wait for, then extract logs from
*/
export async function getLogs(
tx: ContractTransaction,
): Promise<providers.Log[]> {
const receipt = await tx.wait()
if (!receipt.logs) {
throw Error('unable to extract logs from transaction receipt')
}
return receipt.logs
}
/**
* Convert a UTF-8 string into a bytes32 hex string representation
*
* The inverse function of [[parseBytes32String]]
*
* @param args The UTF-8 string representation to convert to a bytes32 hex string representation
*/
export function toBytes32String(
...args: Parameters<typeof ethers.utils.formatBytes32String>
): ReturnType<typeof ethers.utils.formatBytes32String> {
return ethers.utils.formatBytes32String(...args)
}
/**
* Strip the leading 0x hex prefix from a hex string
*
* @param hex The hex string to strip the leading hex prefix out of
*/
export function stripHexPrefix(hex: string): string {
if (!ethers.utils.isHexString(hex)) {
throw Error(`Expected valid hex string, got: "${hex}"`)
}
return hex.replace('0x', '')
}
/**
* Create a buffer from a hex string
*
* @param hexstr The hex string to convert to a buffer
*/
export function hexToBuf(hexstr: string): Buffer {
return Buffer.from(stripHexPrefix(hexstr), 'hex')
}
/**
* Decodes a CBOR hex string, and adds opening and closing brackets to the CBOR if they are not present.
*
* @param hexstr The hex string to decode
*/
export function decodeDietCBOR(hexstr: string) {
const buf = hexToBuf(hexstr)
return cbor.decodeFirstSync(addCBORMapDelimiters(buf))
}
/**
* Add a starting and closing map characters to a CBOR encoding if they are not already present.
*/
export function addCBORMapDelimiters(buffer: Buffer): Buffer {
if (buffer[0] >> 5 === 5) {
return buffer
}
/**
* This is the opening character of a CBOR map.
* @see https://en.wikipedia.org/wiki/CBOR#CBOR_data_item_header
*/
const startIndefiniteLengthMap = Buffer.from([0xbf])
/**
* This is the closing character in a CBOR map.
* @see https://en.wikipedia.org/wiki/CBOR#CBOR_data_item_header
*/
const endIndefiniteLengthMap = Buffer.from([0xff])
return Buffer.concat(
[startIndefiniteLengthMap, buffer, endIndefiniteLengthMap],
buffer.length + 2,
)
}
/**
* Convert an Ether value to a wei amount
*
* @param args Ether value to convert to an Ether amount
*/
export function toWei(
...args: Parameters<typeof ethers.utils.parseEther>
): ReturnType<typeof ethers.utils.parseEther> {
return ethers.utils.parseEther(...args)
}
/**
* Converts any number, BigNumber, hex string or Arrayish to a hex string.
*
* @param args Value to convert to a hex string
*/
export function toHex(
...args: Parameters<typeof ethers.utils.hexlify>
): ReturnType<typeof ethers.utils.hexlify> {
return ethers.utils.hexlify(...args)
}
/**
* Increase the current time within the evm to 5 minutes past the current time
*
* @param provider The ethers provider to send the time increase request to
*/
export async function increaseTime5Minutes(
provider: providers.JsonRpcProvider,
): Promise<void> {
await increaseTimeBy(5 * 60, provider)
}
/**
* Increase the current time within the evm to "n" seconds past the current time
*
* @param seconds The number of seconds to increase to the current time by
* @param provider The ethers provider to send the time increase request to
*/
export async function increaseTimeBy(
seconds: number,
provider: providers.JsonRpcProvider,
) {
await provider.send('evm_increaseTime', [seconds])
}
/**
* Instruct the provider to mine an additional block
*
* @param provider The ethers provider to instruct to mine an additional block
*/
export async function mineBlock(provider: providers.JsonRpcProvider) {
await provider.send('evm_mine', [])
}
/**
* Parse out an evm word (32 bytes) into an address (20 bytes) representation
*
* @param hex The evm word in hex string format to parse the address
* out of.
*/
export function evmWordToAddress(hex?: string): string {
if (!hex) {
throw Error('Input not defined')
}
assert.equal(hex.slice(0, 26), '0x000000000000000000000000')
return ethers.utils.getAddress(hex.slice(26))
}
/**
* Check that a contract's abi exposes the expected interface.
*
* @param contract The contract with the actual abi to check the expected exposed methods and getters against.
* @param expectedPublic The expected public exposed methods and getters to match against the actual abi.
*/
export function publicAbi(
contract: Contract,
expectedPublic: string[],
): boolean {
const actualPublic = []
for (const m in contract.functions) {
if (!m.includes('(')) {
actualPublic.push(m)
}
}
for (const method of actualPublic) {
const index = expectedPublic.indexOf(method)
assert.isAtLeast(index, 0, `#${method} is NOT expected to be public`)
}
for (const method of expectedPublic) {
const index = actualPublic.indexOf(method)
assert.isAtLeast(index, 0, `#${method} is expected to be public`)
}
return true
}
/**
* Converts an L1 address to an Arbitrum L2 address
*
* @param l1Address Address on L1
*/
export function toArbitrumL2AliasAddress(l1Address: string): string {
return ethers.utils.getAddress(
BigNumber.from(l1Address)
.add('0x1111000000000000000000000000000000001111')
.toHexString()
.replace('0x01', '0x'),
)
}
/**
* Lets you impersonate and sign transactions from any account.
*
* @param address Address to impersonate
*/
export async function impersonateAs(
address: string,
): Promise<SignerWithAddress> {
await hre.network.provider.request({
method: 'hardhat_impersonateAccount',
params: [address],
})
return await ethers.getSigner(address)
}
export async function stopImpersonateAs(address: string): Promise<void> {
await hre.network.provider.request({
method: 'hardhat_stopImpersonatingAccount',
params: [address],
})
}
export async function assertBalance(
address: string,
balance: BigNumberish,
msg?: string,
) {
expect(await ethers.provider.getBalance(address)).equal(balance, msg)
}
export async function assertLinkTokenBalance(
lt: LinkToken,
address: string,
balance: BigNumberish,
msg?: string,
) {
expect(await lt.balanceOf(address)).equal(balance, msg)
}
export async function assertSubscriptionBalance(
coordinator: Contract,
subID: BigNumberish,
balance: BigNumberish,
msg?: string,
) {
expect((await coordinator.getSubscription(subID)).balance).deep.equal(
balance,
msg,
)
}
export async function setTimestamp(timestamp: number) {
await network.provider.request({
method: 'evm_setNextBlockTimestamp',
params: [timestamp],
})
await network.provider.request({
method: 'evm_mine',
params: [],
})
}
export async function fastForward(duration: number) {
await network.provider.request({
method: 'evm_increaseTime',
params: [duration],
})
await network.provider.request({
method: 'evm_mine',
params: [],
})
}
export async function reset() {
await network.provider.request({
method: 'hardhat_reset',
params: [],
})
}
export function randomAddress() {
return ethers.Wallet.createRandom().address
}