Skip to content

Commit

Permalink
Migrate account:register to anvil (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
shazarre authored May 22, 2024
1 parent 6314e2e commit 305e278
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 79 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-ghosts-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/dev-utils': patch
---

Introduces testWithAnvil that allows testing against a local anvil instance
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ jobs:
uses: ./.github/actions/sync-workspace
with:
artifacts_to_cache: ${{ needs.install-dependencies.outputs.artifacts_to_cache }}
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: "nightly-f625d0fa7c51e65b4bf1e8f7931cd1c6e2e285e9"
- name: Run tests
run: |
yarn workspace @celo/celocli test --coverage
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 17 additions & 7 deletions packages/cli/src/commands/account/register.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { testWithGanache } from '@celo/dev-utils/lib/ganache-test'
import { newKitFromWeb3 } from '@celo/contractkit'
import { testWithAnvil } from '@celo/dev-utils/lib/anvil-test'
import Web3 from 'web3'
import { testLocally } from '../../test-utils/cliUtils'
import { testLocallyWithWeb3Node } from '../../test-utils/cliUtils'
import Register from './register'

process.env.NO_SYNCCHECK = 'true'

testWithGanache('account:register cmd', (web3: Web3) => {
testWithAnvil('account:register cmd', (web3: Web3) => {
test('can register account', async () => {
const accounts = await web3.eth.getAccounts()

await testLocally(Register, ['--from', accounts[0], '--name', 'Chapulin Colorado'])
await testLocallyWithWeb3Node(
Register,
['--from', accounts[0], '--name', 'Chapulin Colorado'],
web3
)

const kit = newKitFromWeb3(web3)
const account = await kit.contracts.getAccounts()

expect(await account.getName(accounts[0])).toMatchInlineSnapshot(`"Chapulin Colorado"`)
})

test('fails if from is missing', async () => {
// const accounts = await web3.eth.getAccounts()

await expect(testLocally(Register, [])).rejects.toThrow('Missing required flag')
await expect(testLocallyWithWeb3Node(Register, [], web3)).rejects.toThrow(
'Missing required flag'
)
})
})
27 changes: 27 additions & 0 deletions packages/cli/src/test-utils/cliUtils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
import { Interfaces } from '@oclif/core'
import Web3 from 'web3'
import { BaseCommand } from '../base'

type AbstractConstructor<T> = new (...args: any[]) => T
interface Runner extends AbstractConstructor<BaseCommand> {
run: typeof BaseCommand.run
}

export async function testLocallyWithWeb3Node(
command: Runner,
argv: string[],
web3: Web3,
config?: Interfaces.LoadOptions
) {
if (web3.currentProvider instanceof Web3.providers.HttpProvider) {
return testLocally(command, [...argv, '--node', web3.currentProvider.host], config)
}

// CeloProvider is not exported from @celo/connect, but it's injected into web3
if (web3.currentProvider !== null && web3.currentProvider.constructor.name === 'CeloProvider') {
return testLocally(
command,
[...argv, '--node', (web3.currentProvider as any).existingProvider.host],
config
)
}

throw new Error('Unsupported provider')
}

export async function testLocally(
command: Runner,
argv: string[],
config?: Interfaces.LoadOptions
) {
if (argv.includes('--node')) {
return command.run(argv, config)
}

const extendedArgv = [...argv, '--node', 'local']
return command.run(extendedArgv, config)
}
Expand Down
19 changes: 14 additions & 5 deletions packages/cli/src/test-utils/setup.global.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getInstance } from '@celo/dev-utils/lib/anvil-test'
import baseSetup from '@celo/dev-utils/lib/ganache-setup'
// Has to import the matchers somewhere so that typescript knows the matchers have been made available
import _unused from '@celo/dev-utils/lib/matchers'
Expand All @@ -7,13 +8,21 @@ import * as path from 'path'
// If there is not, then your editor probably deleted it automatically.

export default async function globalSetup() {
console.log('\nstarting ganache...')
const anvil = getInstance()

const chainDataPath = path.join(path.dirname(require.resolve('@celo/celo-devchain')), '../chains')
// v X refers to core contract release X
await baseSetup(path.resolve(chainDataPath), 'v11.tar.gz', {
from_targz: true,
})
console.log('\n ganache started...')

console.log('\nStarting anvil & ganache...')

await Promise.all([
anvil.start(),
baseSetup(path.resolve(chainDataPath), 'v11.tar.gz', {
from_targz: true,
}),
])

console.log('\n anvil & ganache started...')
// it is necessary to disabled oclif integration with ts-node as
// together it leads to a silent signit error and exit when tsconfk is loaded.
// @ts-ignore - because global this doesnt have oclif property
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/test-utils/teardown.global.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { getInstance } from '@celo/dev-utils/lib/anvil-test'
import teardown from '@celo/dev-utils/lib/ganache-teardown'

export default async function globalTeardown() {
await teardown()
const anvil = getInstance()

await Promise.all([teardown(), anvil.stop()])
}
1 change: 1 addition & 0 deletions packages/dev-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"prepack": "yarn build"
},
"dependencies": {
"@viem/anvil": "^0.0.9",
"bignumber.js": "^9.0.0",
"fs-extra": "^8.1.0",
"ganache": "npm:@celo/[email protected]",
Expand Down
1 change: 1 addition & 0 deletions packages/dev-utils/src/anvil-state.json

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions packages/dev-utils/src/anvil-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Anvil, createAnvil } from '@viem/anvil'
import { join } from 'path'
import Web3 from 'web3'
import { testWithWeb3 } from './test-utils'

let instance: null | Anvil = null

const ANVIL_PORT = 8546
/*
* This file is generated by running the script at
* https://github.com/celo-org/celo-monorepo/blob/master/packages/protocol/migrations_sol/create_and_migrate_anvil_devchain.sh
*/
const ANVIL_STATE_JSON = 'anvil-state.json'

export function getInstance(): Anvil {
if (instance === null) {
instance = createAnvil({
port: ANVIL_PORT,
loadState: join(__dirname, ANVIL_STATE_JSON),
})
}

return instance
}

export function testWithAnvil(name: string, fn: (web3: Web3) => void) {
return testWithWeb3(name, `http://127.0.0.1:${ANVIL_PORT}`, fn)
}
66 changes: 2 additions & 64 deletions packages/dev-utils/src/ganache-test.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
import Web3 from 'web3'
import { JsonRpcResponse } from 'web3-core-helpers'
import migrationOverride from './migration-override.json'
import { jsonRpcCall, testWithWeb3 } from './test-utils'

export const NetworkConfig = migrationOverride

export function jsonRpcCall<O>(web3: Web3, method: string, params: any[]): Promise<O> {
return new Promise<O>((resolve, reject) => {
if (web3.currentProvider && typeof web3.currentProvider !== 'string') {
web3.currentProvider.send(
{
id: new Date().getTime(),
jsonrpc: '2.0',
method,
params,
},
(err: Error | null, res?: JsonRpcResponse) => {
if (err) {
reject(err)
} else if (!res) {
reject(new Error('no response'))
} else if (res.error) {
reject(
new Error(
`Failed JsonRpcResponse: method: ${method} params: ${JSON.stringify(
params
)} error: ${JSON.stringify(res.error)}`
)
)
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
resolve(res.result)
}
}
)
} else {
reject(new Error('Invalid provider'))
}
})
}

export async function timeTravel(seconds: number, web3: Web3) {
await jsonRpcCall(web3, 'evm_increaseTime', [seconds])
await jsonRpcCall(web3, 'evm_mine', [])
Expand All @@ -50,35 +15,8 @@ export async function mineBlocks(blocks: number, web3: Web3) {
}
}

export function evmRevert(web3: Web3, snapId: string): Promise<void> {
return jsonRpcCall(web3, 'evm_revert', [snapId])
}

export function evmSnapshot(web3: Web3) {
return jsonRpcCall<string>(web3, 'evm_snapshot', [])
}

export function testWithGanache(name: string, fn: (web3: Web3) => void) {
const web3 = new Web3('http://localhost:8545')

describe(name, () => {
let snapId: string | null = null

beforeEach(async () => {
if (snapId != null) {
await evmRevert(web3, snapId)
}
snapId = await evmSnapshot(web3)
})

afterAll(async () => {
if (snapId != null) {
await evmRevert(web3, snapId)
}
})

fn(web3)
})
return testWithWeb3(name, 'http://localhost:8545', fn)
}

/**
Expand Down
71 changes: 71 additions & 0 deletions packages/dev-utils/src/test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Web3 from 'web3'
import { JsonRpcResponse } from 'web3-core-helpers'
import migrationOverride from './migration-override.json'

export const NetworkConfig = migrationOverride

export function jsonRpcCall<O>(web3: Web3, method: string, params: any[]): Promise<O> {
return new Promise<O>((resolve, reject) => {
if (web3.currentProvider && typeof web3.currentProvider !== 'string') {
web3.currentProvider.send(
{
id: new Date().getTime(),
jsonrpc: '2.0',
method,
params,
},
(err: Error | null, res?: JsonRpcResponse) => {
if (err) {
reject(err)
} else if (!res) {
reject(new Error('no response'))
} else if (res.error) {
reject(
new Error(
`Failed JsonRpcResponse: method: ${method} params: ${JSON.stringify(
params
)} error: ${JSON.stringify(res.error)}`
)
)
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
resolve(res.result)
}
}
)
} else {
reject(new Error('Invalid provider'))
}
})
}

export function evmRevert(web3: Web3, snapId: string): Promise<void> {
return jsonRpcCall(web3, 'evm_revert', [snapId])
}

export function evmSnapshot(web3: Web3) {
return jsonRpcCall<string>(web3, 'evm_snapshot', [])
}

export function testWithWeb3(name: string, rpcUrl: string, fn: (web3: Web3) => void) {
const web3 = new Web3(rpcUrl)

describe(name, () => {
let snapId: string | null = null

beforeEach(async () => {
if (snapId != null) {
await evmRevert(web3, snapId)
}
snapId = await evmSnapshot(web3)
})

afterAll(async () => {
if (snapId != null) {
await evmRevert(web3, snapId)
}
})

fn(web3)
})
}
6 changes: 5 additions & 1 deletion packages/dev-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@
"strict": false,
"declaration": true,
},
"include": ["src/**/*", "src/migration-override.json"]
"include": [
"src/**/*",
"src/migration-override.json",
"src/anvil-state.json",
]
}
Loading

0 comments on commit 305e278

Please sign in to comment.