-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
305af0d
commit 1b9ff20
Showing
13 changed files
with
1,531 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
#!/bin/bash | ||
|
||
SCRIPT=$(realpath $0) | ||
SCRIPT_PATH=$(dirname $SCRIPT) | ||
ROOT_PATH=$SCRIPT_PATH/.. | ||
ENVIONRMENT_PATH=$SCRIPT_PATH/../test/bundler-integration/environment | ||
COMPOSE_FILE_PATH=$ENVIONRMENT_PATH/docker-compose.yml | ||
ENTRYPOINT_DEPLOY_SCRIPT_PATH=$ENVIONRMENT_PATH/deployEntrypoint.ts | ||
|
||
docker compose -f $COMPOSE_FILE_PATH down | ||
|
||
echo "⚙️ 1. Launching geth...." | ||
docker compose -f $COMPOSE_FILE_PATH up geth-dev -d | ||
|
||
echo "⚙️ 2. Deploying Entrypoint..." | ||
npx hardhat run $ENTRYPOINT_DEPLOY_SCRIPT_PATH --network local | ||
|
||
source $ENVIONRMENT_PATH/.env | ||
|
||
echo "⚙️ 3. Launching Bundler..." | ||
docker compose -f $COMPOSE_FILE_PATH up bundler -d | ||
|
||
echo "⚙️ 4. Waiting for Bundler to start..." | ||
URL="http://localhost:3000" | ||
JSON_DATA='{ | ||
"jsonrpc": "2.0", | ||
"method": "web3_clientVersion", | ||
"params": [] | ||
}' | ||
while true; do | ||
RESPONSE_CODE=$(curl --write-out '%{http_code}' --silent --output /dev/null --header "Content-Type: application/json" --request POST --data "$JSON_DATA" "$URL") | ||
|
||
if [ "$RESPONSE_CODE" -eq 200 ]; then | ||
echo "Received 200 OK response!" | ||
break | ||
else | ||
echo "Waiting for 200 OK response, got $RESPONSE_CODE. Retrying in 5 seconds..." | ||
sleep 5 | ||
fi | ||
done | ||
|
||
echo "⚙️ 5. Running tests with params --network local $@" | ||
npx hardhat test $(find $ROOT_PATH/test/bundler-integration -type f -name "*.ts") --network local "$@" | ||
|
||
echo "⚙️ 6. Stopping geth and bundler...." | ||
docker compose -f $COMPOSE_FILE_PATH down |
199 changes: 199 additions & 0 deletions
199
test/bundler-integration/environment/bundlerEnvironment.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import { providers, BigNumberish, utils, BigNumber } from "ethers"; | ||
import axios, { AxiosInstance } from "axios"; | ||
import { ethers, config } from "hardhat"; | ||
import type { HttpNetworkConfig } from "hardhat/types"; | ||
import { UserOperation } from "../../../account-abstraction/test/UserOperation"; | ||
import { hexValue } from "ethers/lib/utils"; | ||
|
||
export type Snapshot = { | ||
blockNumber: number; | ||
}; | ||
|
||
export class UserOperationSubmissionError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "UserOperationSubmissionError"; | ||
} | ||
} | ||
export class BundlerResetError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "BundleResetError"; | ||
} | ||
} | ||
|
||
export const serializeUserOp = (op: UserOperation) => ({ | ||
sender: op.sender, | ||
nonce: hexValue(op.nonce), | ||
initCode: op.initCode, | ||
callData: op.callData, | ||
callGasLimit: hexValue(op.callGasLimit), | ||
verificationGasLimit: hexValue(op.verificationGasLimit), | ||
preVerificationGas: hexValue(op.preVerificationGas), | ||
maxFeePerGas: hexValue(op.maxFeePerGas), | ||
maxPriorityFeePerGas: hexValue(op.maxPriorityFeePerGas), | ||
paymasterAndData: op.paymasterAndData, | ||
signature: op.signature, | ||
}); | ||
|
||
export class BundlerTestEnvironment { | ||
public static BUNDLER_ENVIRONMENT_CHAIN_ID = 1337; | ||
public static DEFAULT_FUNDING_AMOUNT = utils.parseEther("1000"); | ||
|
||
DOCKER_COMPOSE_DIR = __dirname; | ||
DOCKER_COMPOSE_BUNDLER_SERVICE = "bundler"; | ||
|
||
private apiClient: AxiosInstance; | ||
public defaultSnapshot: Snapshot | undefined; | ||
private static instance: BundlerTestEnvironment; | ||
|
||
constructor( | ||
public readonly provider: providers.JsonRpcProvider, | ||
public readonly bundlerUrl: string | ||
) { | ||
this.apiClient = axios.create({ | ||
baseURL: this.bundlerUrl, | ||
}); | ||
} | ||
|
||
static getDefaultInstance = async () => { | ||
if (this.instance) { | ||
return this.instance; | ||
} | ||
|
||
this.instance = new BundlerTestEnvironment( | ||
new providers.JsonRpcProvider( | ||
(config.networks.local as HttpNetworkConfig).url | ||
), | ||
"http://localhost:3000" | ||
); | ||
|
||
const defaultAddresses = (await ethers.getSigners()).map( | ||
(signer) => signer.address | ||
); | ||
await this.instance.fundAccounts( | ||
defaultAddresses, | ||
defaultAddresses.map((_) => this.DEFAULT_FUNDING_AMOUNT) | ||
); | ||
|
||
this.instance.defaultSnapshot = await this.instance.snapshot(); | ||
|
||
return this.instance; | ||
}; | ||
|
||
fundAccounts = async ( | ||
accountsToFund: string[], | ||
fundingAmount: BigNumberish[] | ||
) => { | ||
const signer = this.provider.getSigner(); | ||
const nonce = await signer.getTransactionCount(); | ||
accountsToFund = ( | ||
await Promise.all( | ||
accountsToFund.map( | ||
async (account, i): Promise<[string, boolean]> => [ | ||
account, | ||
(await this.provider.getBalance(account)).lt(fundingAmount[i]), | ||
] | ||
) | ||
) | ||
) | ||
.filter(([, needsFunding]) => needsFunding) | ||
.map(([account]) => account); | ||
await Promise.all( | ||
accountsToFund.map((account, i) => | ||
signer.sendTransaction({ | ||
to: account, | ||
value: fundingAmount[i], | ||
nonce: nonce + i, | ||
}) | ||
) | ||
); | ||
}; | ||
|
||
snapshot = async (): Promise<Snapshot> => ({ | ||
blockNumber: await this.provider.getBlockNumber(), | ||
}); | ||
|
||
sendUserOperation = async ( | ||
userOperation: UserOperation, | ||
entrypointAddress: string | ||
): Promise<string> => { | ||
const result = await this.apiClient.post("/rpc", { | ||
jsonrpc: "2.0", | ||
method: "eth_sendUserOperation", | ||
params: [serializeUserOp(userOperation), entrypointAddress], | ||
}); | ||
if (result.status !== 200) { | ||
throw new Error( | ||
`Failed to send user operation: ${JSON.stringify( | ||
result.data.error.message | ||
)}` | ||
); | ||
} | ||
if (result.data.error) { | ||
throw new UserOperationSubmissionError(JSON.stringify(result.data.error)); | ||
} | ||
|
||
return result.data; | ||
}; | ||
|
||
resetBundler = async () => { | ||
const result = await this.apiClient.post("/rpc", { | ||
jsonrpc: "2.0", | ||
method: "debug_bundler_clearState", | ||
params: [], | ||
}); | ||
if (result.status !== 200) { | ||
throw new Error( | ||
`Failed to send reset bundler: ${JSON.stringify( | ||
result.data.error.message | ||
)}` | ||
); | ||
} | ||
if (result.data.error) { | ||
throw new BundlerResetError(JSON.stringify(result.data.error)); | ||
} | ||
|
||
if (result.data.result !== "ok") { | ||
throw new BundlerResetError( | ||
`Failed to reset bundler: ${JSON.stringify(result.data.result)}` | ||
); | ||
} | ||
}; | ||
|
||
dumpMempool = async () => { | ||
const result = await this.apiClient.post("/rpc", { | ||
jsonrpc: "2.0", | ||
method: "debug_bundler_dumpMempool", | ||
params: [], | ||
}); | ||
if (result.status !== 200) { | ||
throw new Error( | ||
`Failed to send reset bundler: ${JSON.stringify( | ||
result.data.error.message | ||
)}` | ||
); | ||
} | ||
if (result.data.error) { | ||
throw new BundlerResetError(JSON.stringify(result.data.error)); | ||
} | ||
|
||
return result.data.result; | ||
}; | ||
|
||
revert = async (snapshot: Snapshot) => { | ||
await this.provider.send("debug_setHead", [ | ||
utils.hexValue(BigNumber.from(snapshot.blockNumber)), | ||
]); | ||
|
||
// getBlockNumber() caches the result, so we directly call the rpc method instead | ||
const currentBlockNumber = BigNumber.from( | ||
await this.provider.send("eth_blockNumber", []) | ||
); | ||
if (!BigNumber.from(snapshot.blockNumber).eq(currentBlockNumber)) { | ||
throw new Error( | ||
`Failed to revert to block ${snapshot.blockNumber}. Current block number is ${currentBlockNumber}` | ||
); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ethers } from "hardhat"; | ||
import { BundlerTestEnvironment } from "./bundlerEnvironment"; | ||
import { promises } from "fs"; | ||
import path from "path"; | ||
import { EntryPoint__factory } from "@account-abstraction/contracts"; | ||
|
||
const envPath = path.join(__dirname, ".env"); | ||
|
||
if (require.main === module) { | ||
(async () => { | ||
await BundlerTestEnvironment.getDefaultInstance(); | ||
const [deployer] = await ethers.getSigners(); | ||
const entrypoint = await new EntryPoint__factory(deployer).deploy(); | ||
console.log("Entrypoint deployed at", entrypoint.address); | ||
await promises.writeFile(envPath, `ENTRYPOINT=${entrypoint.address}`); | ||
})(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
version: "2" | ||
|
||
services: | ||
bundler: | ||
ports: ["3000:3000"] | ||
image: ankurdubeybiconomy/bundler:latest # Image based off accountabstraction/bundler:0.6.1 with fixes for debug_bundler_clearState | ||
command: --network http://geth-dev:8545 --entryPoint ${ENTRYPOINT} --show-stack-traces | ||
volumes: | ||
- ./workdir:/app/workdir:ro | ||
|
||
mem_limit: 1000M | ||
logging: | ||
driver: "json-file" | ||
options: | ||
max-size: 10m | ||
max-file: "10" | ||
|
||
geth-dev: | ||
build: geth-dev | ||
ports: ["8545:8545"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
FROM ethereum/client-go:release-1.12 | ||
ENTRYPOINT geth \ | ||
--http.vhosts '*,localhost,host.docker.internal' \ | ||
--http \ | ||
--http.api personal,eth,net,web3,debug \ | ||
--http.corsdomain '*' \ | ||
--http.addr "0.0.0.0" \ | ||
--nodiscover --maxpeers 0 --mine \ | ||
--networkid 1337 \ | ||
--dev \ | ||
--allow-insecure-unlock \ | ||
--rpc.allow-unprotected-txs \ | ||
--dev.gaslimit 200000000 \ |
12 changes: 12 additions & 0 deletions
12
test/bundler-integration/environment/workdir/bundler.config.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"gasFactor": "1", | ||
"port": "3000", | ||
"beneficiary": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", | ||
"minBalance": "1", | ||
"mnemonic": "./workdir/mnemonic.txt", | ||
"maxBundleGas": 5e6, | ||
"minStake": "1", | ||
"minUnstakeDelay": 0, | ||
"autoBundleInterval": 0, | ||
"autoBundleMempoolSize": 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
test test test test test test test test test test test junk |
Oops, something went wrong.