-
-
Notifications
You must be signed in to change notification settings - Fork 215
add foundry integration #845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
745f381
5bc9a0e
bb58e37
cf16f49
ff68cfc
63fd826
da9df17
f29a5a2
62c2eda
49546fd
d316977
fc6a18c
e5dd29f
8528574
6de7729
0f62a11
53052ab
e6e2eb1
f3f630f
668caba
fe60bc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| const { findNetwork } = require('../helpers'); | ||
| const which = require('which'); | ||
|
|
||
| const log = require('debug')('synpress:foundry'); | ||
|
|
||
| let activeChains; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doing module level state like this is common in synpress and not a good practice in general imo. Instead state should be instanciated by user e.g. |
||
|
|
||
| module.exports = { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should write new code in a .mjs file imo so converting it to TS later is easier |
||
| async resetState() { | ||
| log('Resetting state of foundry'); | ||
| activeChains = undefined; | ||
| }, | ||
| async getActiveChains() { | ||
| return activeChains; | ||
| }, | ||
| async forkChains(options) { | ||
| await validateIfAnvilIsInstalledOrThrow(); | ||
duckception marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (typeof options === 'object') { | ||
| const chains = await module.exports.runAnvilWithViem( | ||
| options.chainsToFork, | ||
| ); | ||
|
|
||
| return { chains }; | ||
| } else if (typeof options === 'string') { | ||
| if (isNaN(options)) { | ||
| // todo: add support for: | ||
| // (multiple) network IDs | ||
| // (single) network name | ||
| // (multiple) network names | ||
duckception marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else { | ||
| // todo: add support for: | ||
| // (single) network ID | ||
| } | ||
|
|
||
| throw new Error('Not implemented'); | ||
| } | ||
| }, | ||
| async setupViem(anvilChainType) { | ||
| try { | ||
| const { | ||
| createTestClient, | ||
| createPublicClient, | ||
| createWalletClient, | ||
| http, | ||
| } = require('viem'); | ||
duckception marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const testClient = createTestClient({ | ||
| chain: anvilChainType, | ||
| mode: 'anvil', | ||
| transport: http(), | ||
| }); | ||
|
|
||
| const publicClient = createPublicClient({ | ||
| chain: anvilChainType, | ||
| transport: http(), | ||
| }); | ||
|
|
||
| const walletClient = createWalletClient({ | ||
| chain: anvilChainType, | ||
| transport: http(), | ||
| }); | ||
|
Comment on lines
+48
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you did the new AnvilSynpressUtil() pattern here the utility could give these to user so user could use them in their tests too |
||
|
|
||
| return { testClient, publicClient, walletClient }; | ||
| } catch (error) { | ||
| throw new Error('There was an error while trying to setup Viem.', error); | ||
| } | ||
| }, | ||
| async runAnvilWithViem(chains) { | ||
| try { | ||
| const { ethers } = require('ethers'); | ||
| const anvilClient = await import('@viem/anvil'); | ||
|
|
||
| const pool = anvilClient.createPool(); | ||
|
|
||
| for (const [index, [chain, options]] of Object.entries( | ||
| Object.entries(chains), | ||
| )) { | ||
| // use fork url if provided, if not then find it in presets | ||
| const forkUrl = | ||
| options.forkUrl || (await findNetwork(chain)).rpcUrls.public.http[0]; | ||
|
|
||
| const poolOptions = { | ||
| ...options, | ||
| forkUrl, | ||
| }; | ||
|
|
||
| // remove nativeCurrency because its not supported by anvil | ||
| if (poolOptions.nativeCurrency) { | ||
| delete poolOptions.nativeCurrency; | ||
| } | ||
|
|
||
| const anvilInstance = await pool.start(index, poolOptions); | ||
|
|
||
| const anvilUrl = `${anvilInstance.host}:${anvilInstance.port}`; | ||
| const provider = new ethers.JsonRpcProvider(`http://${anvilUrl}`); | ||
| const { chainId, name } = await provider.getNetwork(); | ||
| chains[chain].anvilClientDetails = { | ||
| anvilPool: pool, | ||
| anvilPoolId: Number(index), | ||
| provider, | ||
| anvilInstance, | ||
| anvilUrl: `http://${anvilUrl}`, | ||
| anvilChainId: Number(chainId), | ||
| anvilChainName: name, | ||
| anvilChainType: { | ||
| id: Number(chainId), | ||
| name: name, | ||
| network: name, | ||
| nativeCurrency: options.nativeCurrency | ||
| ? options.nativeCurrency | ||
| : { | ||
| decimals: 18, | ||
| name: 'Anvil', | ||
| symbol: 'ANV', | ||
| }, | ||
| rpcUrls: { | ||
| default: { | ||
| http: [`http://${anvilUrl}`], | ||
| webSocket: [`ws://${anvilUrl}`], | ||
| }, | ||
| public: { | ||
| http: [`http://${anvilUrl}`], | ||
| webSocket: [`ws://${anvilUrl}`], | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| chains[chain].viemClients = await module.exports.setupViem( | ||
| chains[chain].anvilClientDetails.anvilChainType, | ||
| ); | ||
| } | ||
|
|
||
| activeChains = chains; | ||
| return chains; | ||
| } catch (error) { | ||
| throw new Error('There was an error while trying to run anvil.', error); | ||
| } | ||
| }, | ||
| async stopAnvil(anvilInstance) { | ||
| try { | ||
| await anvilInstance.stop(); | ||
| return true; | ||
| } catch (error) { | ||
| throw new Error('There was an error while trying to stop anvil.', error); | ||
| } | ||
| }, | ||
| async stopAnvilPoolId(anvilPool, anvilPoolId) { | ||
| try { | ||
| await anvilPool.stop(anvilPoolId); | ||
| } catch (error) { | ||
| throw new Error( | ||
| `There was an error while trying to stop anvil pool with id ${anvilPoolId}`, | ||
| error, | ||
| ); | ||
| } | ||
| }, | ||
| async stopAnvilPool(anvilPool) { | ||
| try { | ||
| if (Object.values(activeChains)[0]) { | ||
| await Object.values( | ||
| activeChains, | ||
| )[0].anvilClientDetails.anvilPool.empty(); | ||
| } else { | ||
| await anvilPool.empty(); | ||
| } | ||
| return true; | ||
| } catch (error) { | ||
| throw new Error( | ||
| `There was an error while trying to stop anvil pool`, | ||
| error, | ||
| ); | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| class AnvilNotInstalledError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = 'AnvilNotInstalledError'; | ||
| } | ||
| } | ||
|
|
||
| async function validateIfAnvilIsInstalledOrThrow() { | ||
| try { | ||
| await which('anvil'); | ||
| } catch (e) { | ||
| throw new AnvilNotInstalledError( | ||
| 'Anvil not detected! Forking is possible thanks to Anvil, a local testnet node shipped with Foundry. To install the Foundry toolchain please refer here: https://book.getfoundry.sh/getting-started/installation', | ||
| ); | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.