-
-
Notifications
You must be signed in to change notification settings - Fork 244
/
fork.js
257 lines (226 loc) · 7.56 KB
/
fork.js
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
const { ethers } = require('ethers')
const ERC20_ABI = require('./ABIs/erc20.json')
const USDC_ABI = require('./ABIs/USDC.json')
const { abi: WETH_ABI } = require('./ABIs/weth.json')
const { MAX_UINT } = require('./constants')
const { getNetwork, getUnlock, getUdt } = require('./unlock')
const { getTokens } = require('./tokens')
const getChainId = () => {
const chainId = parseInt(process.env.RUN_FORK)
if (isNaN(chainId)) {
throw Error(`chain id ('${process.env.RUN_FORK}') should be a number`)
}
return chainId
}
const getForkUrl = () => {
return `https://rpc.unlock-protocol.com/${getChainId()}`
}
async function getWhales(chainId = 1) {
const tokens = await getTokens()
const { address: UDT } = await getUdt()
switch (chainId) {
case 1:
return {
[tokens.DAI]: '0x075e72a5eDf65F0A5f44699c7654C1a76941Ddc8', // PulseX
[tokens.USDC]: '0xD6153F5af5679a75cC85D8974463545181f48772',
[tokens.WBTC]: '0x845cbCb8230197F733b59cFE1795F282786f212C',
[tokens.UDT]: '0xF5C28ce24Acf47849988f147d5C75787c0103534', // unlock-protocol.eth
}
case 137:
return {
[tokens.USDC]: '0xF977814e90dA44bFA03b6295A0616a897441aceC',
[tokens.DAI]: '0x91993f2101cc758D0dEB7279d41e880F7dEFe827',
[tokens.WBTC]: '0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6',
[tokens.UDT]: '0xF5C28ce24Acf47849988f147d5C75787c0103534',
}
default:
break
}
}
// parse mainnet fork
const parseForkUrl = (networks) => {
const chainId = getChainId()
const url = getForkUrl()
const slug = Object.keys(networks).find(
(n) => networks[n].chainId === chainId
)
const { name } = networks[slug]
console.log(`Running a fork of ${name} (${chainId})...`)
networks.hardhat = {
chainId,
forking: {
url,
},
name: `${name} (forked locally)`,
accounts: process.env.DEPLOYER_PRIVATE_KEY
? [
{
privateKey: process.env.DEPLOYER_PRIVATE_KEY,
balance: '10000000000000000000000', // 1000 ETH yah
},
]
: undefined,
}
// needed for Uniswap Router to compute routes on local forks
networks.hardhat.blockGasLimit = 1_000_000_000
// set the correct chainId to use with local node over RPC
networks.localhost.chainId = chainId
networks.localhost.url = 'http://localhost:8545'
}
const resetNodeState = async () => {
const { ethers, network, config } = require('hardhat')
// reset fork
const { forking } = config.networks.hardhat
await network.provider.request({
method: 'hardhat_reset',
params: [
{
forking: {
jsonRpcUrl: forking.url,
blockNumber: forking.blockNumber,
},
},
],
})
}
const addSomeETH = async (address, amount = ethers.parseEther('1000')) => {
const { network } = require('hardhat')
const balance = `0x${BigInt(amount.toString()).toString(16)}`
await network.provider.send('hardhat_setBalance', [address, balance])
}
const impersonate = async (address) => {
const { network, ethers } = require('hardhat')
// provider workaround for hardhat bug
// see https://github.com/NomicFoundation/hardhat/issues/1226#issuecomment-1181706467
let provider
if (network.config.url) {
provider = new ethers.providers.JsonRpcProvider(network.config.url)
} else {
// if network.config.url is undefined, then this is the hardhat network
provider = ethers.provider
}
await provider.send('hardhat_impersonateAccount', [address])
await addSomeETH(address) // give some ETH just in case
// return signer
const signer = provider.getSigner(address)
signer.address = signer._address
return signer
}
const stopImpersonate = async (address) => {
const { network } = require('hardhat')
await network.provider.request({
method: 'hardhat_stopImpersonatingAccount',
params: [address],
})
}
const addERC20 = async function (
tokenAddress,
address,
amount = ethers.parseEther('1000')
) {
const { ethers } = require('hardhat')
const {
nativeCurrency: { wrapped },
} = await getNetwork()
// wrapped some ETH
if (tokenAddress.toLowerCase() === wrapped.toLowerCase()) {
await addSomeETH(address)
const weth = await ethers.getContractAt(WETH_ABI, wrapped)
const signer = await impersonate(address)
const tx = await weth.connect(signer).deposit({ value: amount.toString() })
return weth
}
// special for UDT
const unlock = await getUnlock()
const udt = await unlock.udt()
if (tokenAddress.toLowerCase() === udt.toLowerCase()) {
await addUDT(address, amount)
return await ethers.getContractAt(ERC20_ABI, udt)
}
// otherwise use transfer from whales
const whales = await getWhales()
if (!whales[tokenAddress])
throw Error(`No whale for this address: ${tokenAddress}`)
const whale = await ethers.getSigner(whales[tokenAddress])
await impersonate(whale.address)
const erc20Contract = await ethers.getContractAt(ERC20_ABI, tokenAddress)
await erc20Contract.connect(whale).transfer(address, amount)
return erc20Contract
}
const toBytes32 = (bn) => {
return ethers.hexlify(ethers.zeroPad(bn.toHexString(), 32))
}
const addUDT = async (recipientAddress, amount = 1000, udt) => {
const { ethers, network } = require('hardhat')
const { chainId } = await ethers.provider.getNetwork()
if (!udt) {
udt = await getUdt()
}
// UDT contract
const udtAmount =
typeof amount === 'bigint' ? amount : ethers.parseEther(`${amount}`)
if (chainId === 1 || chainId === 5) {
// NB: slot has been found by using slot20 - see https://kndrck.co/posts/local_erc20_bal_mani_w_hh/
// Get storage slot index
const index = ethers.solidityKeccak256(
['uint256', 'uint256'],
[recipientAddress, 51] // key, slot
)
// Manipulate local balance (needs to be bytes32 string)
await network.provider.request({
method: 'hardhat_setStorageAt',
params: [udt.address, index.toString(), toBytes32(udtAmount).toString()],
})
// Just mines to the next block
await ethers.provider.send('evm_mine', [])
} else {
const unlock_protocol_eth = '0xF5C28ce24Acf47849988f147d5C75787c0103534'
const whale = await ethers.getSigner(unlock_protocol_eth)
await impersonate(unlock_protocol_eth)
await udt.connect(whale).transfer(recipientAddress, amount)
return udt
}
}
const delegates = [
'0x0D8410643ae7a4d833C7219E5C6faDfa5Ce912CD',
'0x6aeC5228fDA60525F59AfC773Ada7df6a6d8e43f',
'0xde22DE740609532FC0F48287b7F258776bE814FD',
]
// main UDT holders on mainnet
const getDelegates = async () => {
return await Promise.all(delegates.map((delegate) => impersonate(delegate)))
}
const getERC20Contract = async (tokenAddress) => {
const { ethers } = require('hardhat')
const {
nativeCurrency: { wrapped },
} = await getNetwork()
const [signer] = await ethers.getSigners()
return tokenAddress === wrapped
? await ethers.getContractAt(WETH_ABI, wrapped, signer)
: await ethers.getContractAt(ERC20_ABI, tokenAddress, signer)
}
const addSomeUSDC = async (usdcAddress, recipientAddress, amount = 1000) => {
const { ethers } = require('hardhat')
const usdc = await ethers.getContractAt(USDC_ABI, usdcAddress)
const masterMinter = await usdc.masterMinter()
await impersonate(masterMinter)
const minter = await ethers.getSigner(masterMinter)
await (
await usdc.connect(minter).configureMinter(recipientAddress, MAX_UINT)
).wait()
await (await usdc.mint(recipientAddress, amount)).wait()
}
module.exports = {
resetNodeState,
impersonate,
stopImpersonate,
getDelegates,
getERC20Contract,
addUDT,
addSomeETH,
addSomeUSDC,
addERC20,
delegates,
parseForkUrl,
}