Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions packages/dev-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@
"fs-extra": "^8.1.0",
"targz": "^1.0.1",
"tmp": "^0.2.0",
"viem": "^2.33.2",
"web3": "1.10.4",
"web3-core-helpers": "1.10.4",
"web3-utils": "1.10.4"
"viem": "^2.33.2"
},
"devDependencies": {
"@celo/base": "workspace:^",
Expand Down
43 changes: 22 additions & 21 deletions packages/dev-utils/src/anvil-test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { StrongAddress } from '@celo/base'
import { Provider } from '@celo/connect'
import { Anvil, CreateAnvilOptions, createAnvil } from '@viem/anvil'
import BigNumber from 'bignumber.js'
import Web3 from 'web3'
import {
TEST_BALANCE,
TEST_BASE_FEE,
TEST_GAS_LIMIT,
TEST_GAS_PRICE,
TEST_MNEMONIC,
jsonRpcCall,
testWithWeb3,
testWithProvider,
} from './test-utils'

let instance: null | Anvil = null
Expand Down Expand Up @@ -44,6 +44,7 @@ function createInstance(stateFilePath: string, chainId?: number): Anvil {
gasLimit: TEST_GAS_LIMIT,
blockBaseFeePerGas: TEST_BASE_FEE,
codeSizeLimit: 50000000,
startTimeout: 60_000,
stopTimeout: 1000,
chainId,
}
Expand All @@ -59,7 +60,7 @@ type TestWithAnvilOptions = {

export function testWithAnvilL2(
name: string,
fn: (web3: Web3) => void,
fn: (provider: Provider) => void,
options?: TestWithAnvilOptions
) {
return testWithAnvil(require.resolve('@celo/devchain-anvil/l2-devchain.json'), name, fn, options)
Expand All @@ -68,13 +69,13 @@ export function testWithAnvilL2(
function testWithAnvil(
stateFilePath: string,
name: string,
fn: (web3: Web3) => void,
fn: (provider: Provider) => void,
options?: TestWithAnvilOptions
) {
const anvil = createInstance(stateFilePath, options?.chainId)

// for each test suite, we start and stop a new anvil instance
return testWithWeb3(name, `http://127.0.0.1:${anvil.port}`, fn, {
return testWithProvider(name, `http://127.0.0.1:${anvil.port}`, fn, {
runIf:
process.env.RUN_ANVIL_TESTS === 'true' || typeof process.env.RUN_ANVIL_TESTS === 'undefined',
hooks: {
Expand All @@ -89,40 +90,40 @@ function testWithAnvil(
}

export function impersonateAccount(
web3: Web3,
provider: Provider,
address: string,
withBalance?: number | bigint | BigNumber
) {
return Promise.all([
jsonRpcCall(web3, 'anvil_impersonateAccount', [address]),
jsonRpcCall(provider, 'anvil_impersonateAccount', [address]),
withBalance
? jsonRpcCall(web3, 'anvil_setBalance', [address, `0x${withBalance.toString(16)}`])
? jsonRpcCall(provider, 'anvil_setBalance', [address, `0x${withBalance.toString(16)}`])
: undefined,
])
}

export function stopImpersonatingAccount(web3: Web3, address: string) {
return jsonRpcCall(web3, 'anvil_stopImpersonatingAccount', [address])
export function stopImpersonatingAccount(provider: Provider, address: string) {
return jsonRpcCall(provider, 'anvil_stopImpersonatingAccount', [address])
}

export const withImpersonatedAccount = async (
web3: Web3,
provider: Provider,
account: string,
fn: () => Promise<void>,
withBalance?: number | bigint | BigNumber
) => {
await impersonateAccount(web3, account, withBalance)
await impersonateAccount(provider, account, withBalance)
await fn()
await stopImpersonatingAccount(web3, account)
await stopImpersonatingAccount(provider, account)
}

export const asCoreContractsOwner = async (
web3: Web3,
provider: Provider,
fn: (ownerAddress: StrongAddress) => Promise<void>,
withBalance?: number | bigint | BigNumber
) => {
await withImpersonatedAccount(
web3,
provider,
DEFAULT_OWNER_ADDRESS,
async () => {
await fn(DEFAULT_OWNER_ADDRESS)
Expand All @@ -131,18 +132,18 @@ export const asCoreContractsOwner = async (
)
}

export function setCode(web3: Web3, address: string, code: string) {
return jsonRpcCall(web3, 'anvil_setCode', [address, code])
export function setCode(provider: Provider, address: string, code: string) {
return jsonRpcCall(provider, 'anvil_setCode', [address, code])
}

export function setNextBlockTimestamp(web3: Web3, timestamp: number) {
return jsonRpcCall(web3, 'evm_setNextBlockTimestamp', [timestamp.toString()])
export function setNextBlockTimestamp(provider: Provider, timestamp: number) {
return jsonRpcCall(provider, 'evm_setNextBlockTimestamp', [timestamp.toString()])
}

export function setBalance(
web3: Web3,
provider: Provider,
address: StrongAddress,
balance: number | bigint | BigNumber
) {
return jsonRpcCall(web3, 'anvil_setBalance', [address, `0x${balance.toString(16)}`])
return jsonRpcCall(provider, 'anvil_setBalance', [address, `0x${balance.toString(16)}`])
}
66 changes: 40 additions & 26 deletions packages/dev-utils/src/chain-setup.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,68 @@
import { governanceABI, validatorsABI } from '@celo/abis'
import { StrongAddress } from '@celo/base'
import Web3 from 'web3'
import { Connection, Provider } from '@celo/connect'
import { DEFAULT_OWNER_ADDRESS, withImpersonatedAccount } from './anvil-test'
import { encodeFunctionData } from 'viem'

export async function setCommissionUpdateDelay(
web3: Web3,
provider: Provider,
validatorsContractAddress: StrongAddress,
delayInBlocks: number
) {
await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => {
// @ts-expect-error
const validators = new web3.eth.Contract(validatorsABI, validatorsContractAddress)

const { transactionHash } = await validators.methods
.setCommissionUpdateDelay(delayInBlocks)
.send({
from: DEFAULT_OWNER_ADDRESS,
})
await web3.eth.getTransactionReceipt(transactionHash)
const conn = new Connection(provider)
await withImpersonatedAccount(provider, DEFAULT_OWNER_ADDRESS, async () => {
const data = encodeFunctionData({
abi: validatorsABI,
functionName: 'setCommissionUpdateDelay',
args: [BigInt(delayInBlocks)],
})
const transactionHash = await conn.sendTransaction({
to: validatorsContractAddress,
data,
from: DEFAULT_OWNER_ADDRESS,
})
await conn.viemClient.waitForTransactionReceipt({ hash: transactionHash })
})
}

export async function setDequeueFrequency(
web3: Web3,
provider: Provider,
governanceContractAddress: StrongAddress,
frequency: number
) {
await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => {
// @ts-expect-error
const governance = new web3.eth.Contract(governanceABI, governanceContractAddress)

const { transactionHash } = await governance.methods.setDequeueFrequency(frequency).send({
const conn = new Connection(provider)
await withImpersonatedAccount(provider, DEFAULT_OWNER_ADDRESS, async () => {
const data = encodeFunctionData({
abi: governanceABI,
functionName: 'setDequeueFrequency',
args: [BigInt(frequency)],
})
const transactionHash = await conn.sendTransaction({
to: governanceContractAddress,
data,
from: DEFAULT_OWNER_ADDRESS,
})
await web3.eth.getTransactionReceipt(transactionHash)
await conn.viemClient.waitForTransactionReceipt({ hash: transactionHash })
})
}

export async function setReferendumStageDuration(
web3: Web3,
provider: Provider,
governanceContractAddress: StrongAddress,
duration: number
) {
await withImpersonatedAccount(web3, DEFAULT_OWNER_ADDRESS, async () => {
// @ts-expect-error
const governance = new web3.eth.Contract(governanceABI, governanceContractAddress)

const { transactionHash } = await governance.methods.setReferendumStageDuration(duration).send({
const conn = new Connection(provider)
await withImpersonatedAccount(provider, DEFAULT_OWNER_ADDRESS, async () => {
const data = encodeFunctionData({
abi: governanceABI,
functionName: 'setReferendumStageDuration',
args: [BigInt(duration)],
})
const transactionHash = await conn.sendTransaction({
to: governanceContractAddress,
data,
from: DEFAULT_OWNER_ADDRESS,
})
await web3.eth.getTransactionReceipt(transactionHash)
await conn.viemClient.waitForTransactionReceipt({ hash: transactionHash })
})
}
33 changes: 18 additions & 15 deletions packages/dev-utils/src/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import { StrongAddress } from '@celo/base'
import { Connection, Provider } from '@celo/connect'
import AttestationsArtifacts from '@celo/celo-devchain/contracts/contracts-0.5/Attestations.json'
import Web3 from 'web3'
import { encodeDeployData } from 'viem'
import { LinkedLibraryAddress } from './anvil-test'
import type { AbiItem } from 'web3-utils'

export const deployAttestationsContract = async (
web3: Web3,
provider: Provider,
owner: StrongAddress
): Promise<StrongAddress> => {
const contract = new web3.eth.Contract(AttestationsArtifacts.abi as AbiItem[])

const deployTx = contract.deploy({
data: AttestationsArtifacts.bytecode.replace(
/__Signatures____________________________/g,
LinkedLibraryAddress.Signatures.replace('0x', '')
),
// By providing true to the contract constructor
// we don't need to call initialize() on the contract
arguments: [true],
const conn = new Connection(provider)
const linkedBytecode = AttestationsArtifacts.bytecode.replace(
/__Signatures____________________________/g,
LinkedLibraryAddress.Signatures.replace('0x', '')
)
const data = encodeDeployData({
abi: AttestationsArtifacts.abi,
bytecode: linkedBytecode as `0x${string}`,
args: [true],
})

const txResult = await deployTx.send({ from: owner })
const txHash = await conn.sendTransaction({
from: owner,
data,
})
const receipt = await conn.viemClient.waitForTransactionReceipt({ hash: txHash })

return txResult.options.address as StrongAddress
return receipt.contractAddress as StrongAddress
}
34 changes: 19 additions & 15 deletions packages/dev-utils/src/ganache-test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,51 @@
import Web3 from 'web3'
import { Provider } from '@celo/connect'
import { getAddress, keccak256, toBytes } from 'viem'
import migrationOverride from './migration-override.json'
import { jsonRpcCall } from './test-utils'

export const NetworkConfig = migrationOverride

export async function timeTravel(seconds: number, web3: Web3) {
await jsonRpcCall(web3, 'evm_increaseTime', [seconds])
await jsonRpcCall(web3, 'evm_mine', [])
export async function timeTravel(seconds: number, provider: Provider) {
await jsonRpcCall(provider, 'evm_increaseTime', [seconds])
await jsonRpcCall(provider, 'evm_mine', [])
}

export async function mineBlocks(blocks: number, web3: Web3) {
export async function mineBlocks(blocks: number, provider: Provider) {
for (let i = 0; i < blocks; i++) {
await jsonRpcCall(web3, 'evm_mine', [])
await jsonRpcCall(provider, 'evm_mine', [])
}
}
/**
* Gets a contract address by parsing blocks and matching event signatures against the given event.
*/
export async function getContractFromEvent(
eventSignature: string,
web3: Web3,
provider: Provider,
filter?: {
expectedData?: string
index?: number
}
): Promise<string> {
const logs = await web3.eth.getPastLogs({
topics: [web3.utils.sha3(eventSignature)],
fromBlock: 'earliest',
toBlock: 'latest',
})
const topic = keccak256(toBytes(eventSignature))
const logs = await jsonRpcCall<any[]>(provider, 'eth_getLogs', [
{
topics: [topic],
fromBlock: 'earliest',
toBlock: 'latest',
},
])
if (logs.length === 0) {
throw new Error(`Error: contract could not be found matching signature ${eventSignature}`)
}
const logIndex = filter?.index ?? 0
if (!filter?.expectedData) {
return logs[logIndex].address
return getAddress(logs[logIndex].address)
}
const filteredLogs = logs.filter((log) => log.data === filter.expectedData)
const filteredLogs = logs.filter((log: { data: string }) => log.data === filter.expectedData)
if (filteredLogs.length === 0) {
throw new Error(
`Error: contract could not be found matching signature ${eventSignature} with data ${filter.expectedData}`
)
}
return filteredLogs[logIndex ?? 0].address
return getAddress(filteredLogs[logIndex ?? 0].address)
}
Loading
Loading