use new account-abstraction/contracts v0.7
This commit is contained in:
Dror Tirosh
2024-02-22 12:42:57 +02:00
committed by GitHub
parent 42cad15044
commit 9ab1a05eb3
65 changed files with 1353 additions and 726 deletions

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "submodules/account-abstraction"]
path = submodules/account-abstraction
url = https://github.com/eth-infinitism/account-abstraction.git
branch = fix-deployment
[submodule "submodules/account-abstraction/"]
branch = fix-deployment

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

View File

@@ -34,7 +34,7 @@ docker run --rm -ti --name geth -p 8545:8545 ethereum/client-go:v1.10.26 \
Now your bundler is active on local url http://localhost:3000/rpc
To run a simple test, do `yarn run runop --deployFactory --network http://localhost:8545/ --entryPoint 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`
To run a simple test, do `yarn run runop --deployFactory --network http://localhost:8545/ --entryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032`
The runop script:
- deploys a wallet deployer (if not already there)

View File

@@ -1,7 +1,7 @@
{
"gasFactor": "1",
"port": "3000",
"entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
"entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
"beneficiary": "0xd21934eD8eAf27a67f0A70042Af50A1D6d195E81",
"minBalance": "1",
"mnemonic": "./workdir/mnemonic.txt",

View File

@@ -6,7 +6,8 @@
"license": "MIT",
"workspaces": {
"packages": [
"packages/*"
"packages/*",
"submodules/account-abstraction/contracts"
],
"nohoist": [
"**eslint**"
@@ -17,20 +18,23 @@
"runop": "yarn --cwd packages/bundler runop",
"runop-goerli": "yarn runop --network goerli --unsafe",
"create-all-deps": "jq '.dependencies,.devDependencies' packages/*/package.json |sort -u > all.deps",
"depcheck": "lerna exec --no-bail --stream --parallel -- npx depcheck",
"depcheck": "lerna exec --no-bail --stream --parallel -- npx depcheck --ignores @account-abstraction/contracts,@openzeppelin/contracts,@uniswap/v3-periphery",
"hardhat-node": "lerna run hardhat-node --stream --no-prefix --",
"hardhat-deploy": "lerna run hardhat-deploy --stream --no-prefix --",
"lerna-clear": "lerna run clear",
"lerna-lint": "lerna run lint --stream --parallel -- ",
"lerna-test": "lerna run hardhat-test --stream",
"lerna-test": "lerna run hardhat-test --stream --",
"lerna-tsc": "lerna run tsc --scope @account-abstraction/*",
"lerna-watch-tsc": "lerna run --parallel watch-tsc",
"lint-fix": "lerna run lint-fix --parallel",
"clear": "lerna run clear",
"hardhat-compile": "lerna run hardhat-compile",
"preprocess": "yarn lerna-clear && yarn hardhat-compile && yarn lerna-tsc",
"preprocess": "yarn preprocess1 && yarn preprocess2",
"preprocess2": "yarn lerna-clear && yarn hardhat-compile && yarn lerna-tsc",
"preprocess1": "yarn submodule-update && cd submodules/account-abstraction && yarn && yarn --cwd contracts prepack",
"submodule-update": "git submodule update --remote --init --recursive",
"runop-self": "ts-node ./packages/bundler/src/runner/runop.ts --deployFactory --selfBundler --unsafe",
"ci": "env ; yarn depcheck; yarn preprocess ; yarn lerna-test"
"ci": "env && yarn depcheck && yarn preprocess ; yarn lerna-test"
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^5.33.0",

View File

@@ -6,12 +6,12 @@ import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
contract GetUserOpHashes {
error UserOpHashesResult(bytes32[] userOpHashes);
constructor(IEntryPoint entryPoint, UserOperation[] memory userOps) {
constructor(IEntryPoint entryPoint, PackedUserOperation[] memory userOps) {
revert UserOpHashesResult(
getUserOpHashes(entryPoint, userOps));
}
function getUserOpHashes(IEntryPoint entryPoint, UserOperation[] memory userOps) public view returns (bytes32[] memory ret) {
function getUserOpHashes(IEntryPoint entryPoint, PackedUserOperation[] memory userOps) public view returns (bytes32[] memory ret) {
ret = new bytes32[](userOps.length);
for (uint i = 0; i < userOps.length; i++) {
ret[i] = entryPoint.getUserOpHash(userOps[i]);

View File

@@ -16,7 +16,7 @@ contract TestRecursionAccount is TestRuleAccount {
function runRule(string memory rule) public virtual override returns (uint) {
if (eq(rule, "handleOps")) {
UserOperation[] memory ops = new UserOperation[](0);
PackedUserOperation[] memory ops = new PackedUserOperation[](0);
ep.handleOps(ops, payable(address (1)));
return 0;
}

View File

@@ -4,6 +4,7 @@ pragma solidity ^0.8.15;
import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "@account-abstraction/contracts/core/UserOperationLib.sol";
/**
* contract for testing account interaction.
@@ -42,7 +43,7 @@ contract TestRuleAccount is IAccount, IPaymaster {
entryPoint.addStake{value : msg.value}(1);
}
function validateUserOp(UserOperation calldata userOp, bytes32, uint256 missingAccountFunds)
function validateUserOp(PackedUserOperation calldata userOp, bytes32, uint256 missingAccountFunds)
external virtual override returns (uint256) {
if (missingAccountFunds > 0) {
/* solhint-disable-next-line avoid-low-level-calls */
@@ -53,14 +54,14 @@ contract TestRuleAccount is IAccount, IPaymaster {
return 0;
}
function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256)
function validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32, uint256)
public virtual override returns (bytes memory context, uint256 deadline) {
string memory rule = string(userOp.paymasterAndData[20 :]);
string memory rule = string(userOp.paymasterAndData[UserOperationLib.PAYMASTER_DATA_OFFSET :]);
runRule(rule);
return ("", 0);
}
function postOp(PostOpMode, bytes calldata, uint256) external {}
function postOp(PostOpMode, bytes calldata, uint256, uint256) external {}
}
contract TestRuleAccountFactory {

View File

@@ -4,6 +4,7 @@ pragma solidity ^0.8.15;
import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "@account-abstraction/contracts/core/UserOperationLib.sol";
import "./TestCoin.sol";
contract Dummy {
@@ -65,7 +66,7 @@ contract TestRulesAccount is IAccount, IPaymaster {
entryPoint.addStake{value : msg.value}(1);
}
function validateUserOp(UserOperation calldata userOp, bytes32, uint256 missingAccountFunds)
function validateUserOp(PackedUserOperation calldata userOp, bytes32, uint256 missingAccountFunds)
external override returns (uint256) {
if (missingAccountFunds > 0) {
/* solhint-disable-next-line avoid-low-level-calls */
@@ -80,14 +81,14 @@ contract TestRulesAccount is IAccount, IPaymaster {
return 0;
}
function validatePaymasterUserOp(UserOperation calldata userOp, bytes32, uint256)
function validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32, uint256)
external returns (bytes memory context, uint256 deadline) {
string memory rule = string(userOp.paymasterAndData[20 :]);
string memory rule = string(userOp.paymasterAndData[UserOperationLib.PAYMASTER_DATA_OFFSET :]);
runRule(rule);
return ("", 0);
}
function postOp(PostOpMode, bytes calldata, uint256) external {}
function postOp(PostOpMode, bytes calldata, uint256, uint256) external {}
}

View File

@@ -4,6 +4,7 @@ pragma solidity ^0.8.15;
import "@account-abstraction/contracts/interfaces/IAccount.sol";
import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "@account-abstraction/contracts/core/UserOperationLib.sol";
import "./TestRuleAccount.sol";
import "./TestCoin.sol";
@@ -21,9 +22,9 @@ contract TestStorageAccount is TestRuleAccount {
event TestMessage(address eventSender);
function validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
function validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
public virtual override returns (bytes memory context, uint256 deadline) {
string memory rule = string(userOp.paymasterAndData[20 :]);
string memory rule = string(userOp.paymasterAndData[UserOperationLib.PAYMASTER_DATA_OFFSET :]);
if (eq(rule, 'postOp-context')) {
return ("some-context", 0);
}

View File

@@ -3,6 +3,7 @@ pragma solidity ^0.8.15;
import "@account-abstraction/contracts/interfaces/IAccount.sol";
import {_packValidationData} from "@account-abstraction/contracts/core/Helpers.sol";
import "@account-abstraction/contracts/core/UserOperationLib.sol";
/**
* test for time-range.
@@ -11,11 +12,13 @@ import {_packValidationData} from "@account-abstraction/contracts/core/Helpers.s
*/
contract TestTimeRangeAccount is IAccount {
function validateUserOp(UserOperation calldata userOp, bytes32, uint256)
using UserOperationLib for PackedUserOperation;
function validateUserOp(PackedUserOperation calldata userOp, bytes32, uint256)
external virtual override returns (uint256) {
uint48 validAfter = uint48(userOp.preVerificationGas);
uint48 validUntil = uint48(userOp.maxPriorityFeePerGas);
uint48 validUntil = uint48(userOp.unpackMaxPriorityFeePerGas());
return _packValidationData(false, validUntil, validAfter);
}
}

View File

@@ -29,7 +29,7 @@ contract TestFakeWalletToken is IAccount {
anotherWallet = _anotherWallet;
}
function validateUserOp(UserOperation calldata userOp, bytes32, uint256)
function validateUserOp(PackedUserOperation calldata userOp, bytes32, uint256)
public override returns (uint256 validationData) {
if (userOp.callData.length == 20) {
// the first UserOperation sets the second sender's "associated" balance to 0

View File

@@ -1,25 +1,23 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { DeployFunction } from 'hardhat-deploy/types'
import { ethers } from 'hardhat'
import { DeterministicDeployer } from '@account-abstraction/sdk'
import { EntryPoint__factory } from '@account-abstraction/contracts'
import { deployEntryPoint, getEntryPointAddress } from '@account-abstraction/utils'
// deploy entrypoint - but only on debug network..
const deployEP: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const dep = new DeterministicDeployer(ethers.provider)
const epAddr = DeterministicDeployer.getAddress(EntryPoint__factory.bytecode)
if (await dep.isContractDeployed(epAddr)) {
const epAddr = getEntryPointAddress()
if (await ethers.provider.getCode(epAddr) !== '0x') {
console.log('EntryPoint already deployed at', epAddr)
return
}
const net = await hre.ethers.provider.getNetwork()
const net = await ethers.provider.getNetwork()
if (net.chainId !== 1337 && net.chainId !== 31337) {
console.log('NOT deploying EntryPoint. use pre-deployed entrypoint')
process.exit(1)
}
await dep.deterministicDeploy(EntryPoint__factory.bytecode)
await deployEntryPoint(ethers.provider)
console.log('Deployed EntryPoint at', epAddr)
}

View File

@@ -41,8 +41,9 @@ const config: HardhatUserConfig = {
goerli: getInfuraNetwork('goerli')
},
solidity: {
version: '0.8.15',
version: '0.8.23',
settings: {
evmVersion: 'paris',
optimizer: { enabled: true }
}
}

View File

@@ -2,7 +2,7 @@
"gasFactor": "1",
"port": "3000",
"network": "http://127.0.0.1:8545",
"entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
"entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
"beneficiary": "0xd21934eD8eAf27a67f0A70042Af50A1D6d195E81",
"minBalance": "1",
"mnemonic": "./localconfig/mnemonic.txt",

View File

@@ -1,6 +1,6 @@
{
"name": "@account-abstraction/bundler",
"version": "0.6.2",
"version": "0.7.0",
"license": "MIT",
"private": true,
"files": [
@@ -23,12 +23,11 @@
"watch-tsc": "tsc -w --preserveWatchOutput"
},
"dependencies": {
"@account-abstraction/contracts": "^0.6.0",
"@account-abstraction/sdk": "^0.6.0",
"@account-abstraction/utils": "^0.6.0",
"@account-abstraction/validation-manager": "^0.6.0",
"@account-abstraction/contracts": "^0.7.0",
"@account-abstraction/sdk": "^0.7.0",
"@account-abstraction/utils": "^0.7.0",
"@account-abstraction/validation-manager": "^0.7.0",
"@ethersproject/abi": "^5.7.0",
"@ethersproject/properties": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
@@ -54,7 +53,7 @@
"@types/node": "^16.4.12",
"body-parser": "^1.20.0",
"chai": "^4.2.0",
"hardhat": "^2.11.0",
"hardhat": "^2.17.0",
"hardhat-deploy": "^0.11.11",
"solidity-coverage": "^0.7.21",
"ts-node": ">=8.0.0",

View File

@@ -50,7 +50,7 @@ export const BundlerConfigShape = {
// TODO: implement merging config (args -> config.js -> default) and runtime shape validation
export const bundlerConfigDefault: Partial<BundlerConfig> = {
port: '3000',
entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789',
entryPoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
unsafe: false,
conditionalRpc: false,
minStake: MIN_STAKE_VALUE,

View File

@@ -5,12 +5,18 @@ import { Provider } from '@ethersproject/providers'
import { Signer, utils } from 'ethers'
import { parseEther } from 'ethers/lib/utils'
import { AddressZero, deepHexlify, erc4337RuntimeVersion, RpcError } from '@account-abstraction/utils'
import {
AddressZero, decodeRevertReason,
deepHexlify, IEntryPoint__factory,
erc4337RuntimeVersion,
packUserOp,
RpcError,
UserOperation
} from '@account-abstraction/utils'
import { BundlerConfig } from './BundlerConfig'
import { UserOpMethodHandler } from './UserOpMethodHandler'
import { Server } from 'http'
import { EntryPoint__factory, UserOperationStruct } from '@account-abstraction/contracts'
import { DebugMethodHandler } from './DebugMethodHandler'
import Debug from 'debug'
@@ -19,6 +25,7 @@ const debug = Debug('aa.rpc')
export class BundlerServer {
app: Express
private readonly httpServer: Server
public silent = false
constructor (
readonly methodHandler: UserOpMethodHandler,
@@ -57,11 +64,9 @@ export class BundlerServer {
}
// minimal UserOp to revert with "FailedOp"
const emptyUserOp: UserOperationStruct = {
const emptyUserOp: UserOperation = {
sender: AddressZero,
callData: '0x',
initCode: AddressZero,
paymasterAndData: '0x',
nonce: 0,
preVerificationGas: 0,
verificationGasLimit: 100000,
@@ -71,18 +76,19 @@ export class BundlerServer {
signature: '0x'
}
// await EntryPoint__factory.connect(this.config.entryPoint,this.provider).callStatic.addStake(0)
const err = await EntryPoint__factory.connect(this.config.entryPoint, this.provider).callStatic.simulateValidation(emptyUserOp)
.catch(e => e)
if (err?.errorName !== 'FailedOp') {
this.fatal(`Invalid entryPoint contract at ${this.config.entryPoint}. wrong version?`)
try {
await IEntryPoint__factory.connect(this.config.entryPoint, this.provider).callStatic.getUserOpHash(packUserOp(emptyUserOp))
} catch (e: any) {
this.fatal(`Invalid entryPoint contract at ${this.config.entryPoint}. wrong version? ${decodeRevertReason(e, false) as string}`)
}
const signerAddress = await this.wallet.getAddress()
const bal = await this.provider.getBalance(signerAddress)
console.log('signer', signerAddress, 'balance', utils.formatEther(bal))
this.log('signer', signerAddress, 'balance', utils.formatEther(bal))
if (bal.eq(0)) {
this.fatal('cannot run with zero balance')
} else if (bal.lt(parseEther(this.config.minBalance))) {
console.log('WARNING: initial balance below --minBalance ', this.config.minBalance)
this.log('WARNING: initial balance below --minBalance ', this.config.minBalance)
}
}
@@ -114,7 +120,7 @@ export class BundlerServer {
data: err.data,
code: err.code
}
console.log('failed: ', 'rpc::res.send()', 'error:', JSON.stringify(error))
this.log('failed: ', 'rpc::res.send()', 'error:', JSON.stringify(error))
}
}
@@ -128,7 +134,7 @@ export class BundlerServer {
debug('>>', { jsonrpc, id, method, params })
try {
const result = deepHexlify(await this.handleMethod(method, params))
console.log('sent', method, '-', result)
debug('sent', method, '-', result)
debug('<<', { jsonrpc, id, result })
return {
jsonrpc,
@@ -141,7 +147,7 @@ export class BundlerServer {
data: err.data,
code: err.code
}
console.log('failed: ', method, 'error:', JSON.stringify(error))
this.log('failed: ', method, 'error:', JSON.stringify(error), err)
debug('<<', { jsonrpc, id, error })
return {
jsonrpc,
@@ -221,4 +227,10 @@ export class BundlerServer {
}
return result
}
log (...params: any[]): void {
if (!this.silent) {
console.log(...arguments)
}
}
}

View File

@@ -1,14 +1,30 @@
import { BigNumber, BigNumberish, Signer } from 'ethers'
import { Log, Provider } from '@ethersproject/providers'
import { JsonRpcProvider, Log, Provider } from '@ethersproject/providers'
import { BundlerConfig } from './BundlerConfig'
import { resolveProperties } from 'ethers/lib/utils'
import { UserOperation, deepHexlify, erc4337RuntimeVersion, requireCond, RpcError, tostr, getAddr, ValidationErrors } from '@account-abstraction/utils'
import { UserOperationStruct, EntryPoint } from '@account-abstraction/contracts'
import { UserOperationEventEvent } from '@account-abstraction/contracts/dist/types/EntryPoint'
import { calcPreVerificationGas } from '@account-abstraction/sdk'
import {
UserOperation,
deepHexlify,
erc4337RuntimeVersion,
requireCond,
tostr,
requireAddressAndFields,
packUserOp,
PackedUserOperation,
unpackUserOp,
simulationRpcParams,
decodeSimulateHandleOpResult,
AddressZero,
ValidationErrors,
RpcError,
decodeRevertReason,
mergeValidationDataValues,
UserOperationEventEvent, IEntryPoint
} from '@account-abstraction/utils'
import { ExecutionManager } from './modules/ExecutionManager'
import { UserOperationByHashResponse, UserOperationReceipt } from './RpcTypes'
import { calcPreVerificationGas } from '@account-abstraction/sdk'
import { EventFragment } from '@ethersproject/abi'
const HEX_REGEX = /^0x[a-fA-F\d]*$/i
@@ -46,7 +62,7 @@ export class UserOpMethodHandler {
readonly provider: Provider,
readonly signer: Signer,
readonly config: BundlerConfig,
readonly entryPoint: EntryPoint
readonly entryPoint: IEntryPoint
) {
}
@@ -65,7 +81,7 @@ export class UserOpMethodHandler {
return beneficiary
}
async _validateParameters (userOp1: UserOperationStruct, entryPointInput: string, requireSignature = true, requireGasParams = true): Promise<void> {
async _validateParameters (userOp1: UserOperation, entryPointInput: string, requireSignature = true, requireGasParams = true): Promise<void> {
requireCond(entryPointInput != null, 'No entryPoint param', -32602)
if (entryPointInput?.toString().toLowerCase() !== this.config.entryPoint.toLowerCase()) {
@@ -73,9 +89,9 @@ export class UserOpMethodHandler {
}
// minimal sanity check: userOp exists, and all members are hex
requireCond(userOp1 != null, 'No UserOperation param')
const userOp = await resolveProperties(userOp1) as any
const userOp = userOp1 as any
const fields = ['sender', 'nonce', 'initCode', 'callData', 'paymasterAndData']
const fields = ['sender', 'nonce', 'callData']
if (requireSignature) {
fields.push('signature')
}
@@ -83,10 +99,12 @@ export class UserOpMethodHandler {
fields.push('preVerificationGas', 'verificationGasLimit', 'callGasLimit', 'maxFeePerGas', 'maxPriorityFeePerGas')
}
fields.forEach(key => {
requireCond(userOp[key] != null, 'Missing userOp field: ' + key + JSON.stringify(userOp), -32602)
requireCond(userOp[key] != null, 'Missing userOp field: ' + key, -32602)
const value: string = userOp[key].toString()
requireCond(value.match(HEX_REGEX) != null, `Invalid hex value for property ${key}:${value} in UserOp`, -32602)
})
requireAddressAndFields(userOp, 'paymaster', ['paymasterPostOpGasLimit', 'paymasterVerificationGasLimit'], ['paymasterData'])
requireAddressAndFields(userOp, 'factory', ['factoryData'])
}
/**
@@ -94,36 +112,38 @@ export class UserOpMethodHandler {
* @param userOp1 input userOp (may have gas fields missing, so they can be estimated)
* @param entryPointInput
*/
async estimateUserOperationGas (userOp1: UserOperationStruct, entryPointInput: string): Promise<EstimateUserOpGasResult> {
const userOp = {
async estimateUserOperationGas (userOp1: Partial<UserOperation>, entryPointInput: string): Promise<EstimateUserOpGasResult> {
const userOp: UserOperation = {
// default values for missing fields.
paymasterAndData: '0x',
maxFeePerGas: 0,
maxPriorityFeePerGas: 0,
preVerificationGas: 0,
verificationGasLimit: 10e6,
...await resolveProperties(userOp1) as any
}
...userOp1
} as any
// todo: checks the existence of parameters, but since we hexlify the inputs, it fails to validate
await this._validateParameters(deepHexlify(userOp), entryPointInput)
// todo: validation manager duplicate?
const errorResult = await this.entryPoint.callStatic.simulateValidation(userOp).catch(e => e)
if (errorResult.errorName === 'FailedOp') {
throw new RpcError(errorResult.errorArgs.at(-1), ValidationErrors.SimulateValidation)
}
// todo throw valid rpc error
if (errorResult.errorName !== 'ValidationResult') {
throw errorResult
}
const provider = this.provider as JsonRpcProvider
const rpcParams = simulationRpcParams('simulateHandleOp', this.entryPoint.address, userOp, [AddressZero, '0x'],
{
// allow estimation when account's balance is zero.
// todo: need a way to flag this, and not enable always.
// [userOp.sender]: {
// balance: hexStripZeros(parseEther('1').toHexString())
// }
})
const ret = await provider.send('eth_call', rpcParams)
.catch((e: any) => { throw new RpcError(decodeRevertReason(e) as string, ValidationErrors.SimulateValidation) })
const { returnInfo } = errorResult.errorArgs
let {
preOpGas,
validAfter,
validUntil
const returnInfo = decodeSimulateHandleOpResult(ret)
const { validAfter, validUntil } = mergeValidationDataValues(returnInfo.accountValidationData, returnInfo.paymasterValidationData)
const {
preOpGas
} = returnInfo
// todo: use simulateHandleOp for this too...
const callGasLimit = await this.provider.estimateGas({
from: this.entryPoint.address,
to: userOp.sender,
@@ -132,14 +152,7 @@ export class UserOpMethodHandler {
const message = err.message.match(/reason="(.*?)"/)?.at(1) ?? 'execution reverted'
throw new RpcError(message, ValidationErrors.UserOperationReverted)
})
validAfter = BigNumber.from(validAfter)
validUntil = BigNumber.from(validUntil)
if ((validUntil as BigNumber).eq(0)) {
validUntil = undefined
}
if ((validAfter as BigNumber).eq(0)) {
validAfter = undefined
}
const preVerificationGas = calcPreVerificationGas(userOp)
const verificationGasLimit = BigNumber.from(preOpGas).toNumber()
return {
@@ -151,15 +164,12 @@ export class UserOpMethodHandler {
}
}
async sendUserOperation (userOp1: UserOperationStruct, entryPointInput: string): Promise<string> {
await this._validateParameters(userOp1, entryPointInput)
async sendUserOperation (userOp: UserOperation, entryPointInput: string): Promise<string> {
await this._validateParameters(userOp, entryPointInput)
const userOp = await resolveProperties(userOp1)
console.log(`UserOperation: Sender=${userOp.sender} Nonce=${tostr(userOp.nonce)} EntryPoint=${entryPointInput} Paymaster=${getAddr(
userOp.paymasterAndData)}`)
console.log(`UserOperation: Sender=${userOp.sender} Nonce=${tostr(userOp.nonce)} EntryPoint=${entryPointInput} Paymaster=${userOp.paymaster ?? ''}`)
await this.execManager.sendUserOperation(userOp, entryPointInput)
return await this.entryPoint.getUserOpHash(userOp)
return await this.entryPoint.getUserOpHash(packUserOp(userOp))
}
async _getUserOperationEvent (userOpHash: string): Promise<UserOperationEventEvent> {
@@ -176,7 +186,7 @@ export class UserOpMethodHandler {
let endIndex = -1
const events = Object.values(this.entryPoint.interface.events)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const beforeExecutionTopic = this.entryPoint.interface.getEventTopic(events.find(e => e.name === 'BeforeExecution')!)
const beforeExecutionTopic = this.entryPoint.interface.getEventTopic(events.find((e: EventFragment) => e.name === 'BeforeExecution')!)
logs.forEach((log, index) => {
if (log?.topics[0] === beforeExecutionTopic) {
// all UserOp execution events start after the "BeforeExecution" event.
@@ -211,7 +221,7 @@ export class UserOpMethodHandler {
throw new Error('unable to parse transaction')
}
const parsed = this.entryPoint.interface.parseTransaction(tx)
const ops: UserOperation[] = parsed?.args.ops
const ops: PackedUserOperation[] = parsed?.args.ops
if (ops == null) {
throw new Error('failed to parse transaction')
}
@@ -223,34 +233,8 @@ export class UserOpMethodHandler {
throw new Error('unable to find userOp in transaction')
}
const {
sender,
nonce,
initCode,
callData,
callGasLimit,
verificationGasLimit,
preVerificationGas,
maxFeePerGas,
maxPriorityFeePerGas,
paymasterAndData,
signature
} = op
return deepHexlify({
userOperation: {
sender,
nonce,
initCode,
callData,
callGasLimit,
verificationGasLimit,
preVerificationGas,
maxFeePerGas,
maxPriorityFeePerGas,
paymasterAndData,
signature
},
userOperation: unpackUserOp(op),
entryPoint: this.entryPoint.address,
transactionHash: tx.hash,
blockHash: tx.blockHash ?? '',

View File

@@ -1,4 +1,3 @@
import { EntryPoint } from '@account-abstraction/contracts'
import { MempoolManager } from './MempoolManager'
import { ValidateUserOpResult, ValidationManager } from '@account-abstraction/validation-manager'
import { BigNumber, BigNumberish } from 'ethers'
@@ -7,7 +6,13 @@ import Debug from 'debug'
import { ReputationManager, ReputationStatus } from './ReputationManager'
import { Mutex } from 'async-mutex'
import { GetUserOpHashes__factory } from '../types'
import { UserOperation, StorageMap, getAddr, mergeStorageMap, runContractScript } from '@account-abstraction/utils'
import {
UserOperation,
StorageMap,
mergeStorageMap,
runContractScript,
packUserOp, IEntryPoint
} from '@account-abstraction/utils'
import { EventsManager } from './EventsManager'
import { ErrorDescription } from '@ethersproject/abi/lib/interface'
@@ -26,7 +31,7 @@ export class BundleManager {
mutex = new Mutex()
constructor (
readonly entryPoint: EntryPoint,
readonly entryPoint: IEntryPoint,
readonly eventsManager: EventsManager,
readonly mempoolManager: MempoolManager,
readonly validationManager: ValidationManager,
@@ -79,7 +84,7 @@ export class BundleManager {
async sendBundle (userOps: UserOperation[], beneficiary: string, storageMap: StorageMap): Promise<SendBundleReturn | undefined> {
try {
const feeData = await this.provider.getFeeData()
const tx = await this.entryPoint.populateTransaction.handleOps(userOps, beneficiary, {
const tx = await this.entryPoint.populateTransaction.handleOps(userOps.map(packUserOp), beneficiary, {
type: 2,
nonce: await this.signer.getTransactionCount(),
gasLimit: 10e6,
@@ -87,18 +92,20 @@ export class BundleManager {
maxFeePerGas: feeData.maxFeePerGas ?? 0
})
tx.chainId = this.provider._network.chainId
const signedTx = await this.signer.signTransaction(tx)
let ret: string
if (this.conditionalRpc) {
const signedTx = await this.signer.signTransaction(tx)
debug('eth_sendRawTransactionConditional', storageMap)
ret = await this.provider.send('eth_sendRawTransactionConditional', [
signedTx, { knownAccounts: storageMap }
])
debug('eth_sendRawTransactionConditional ret=', ret)
} else {
// ret = await this.signer.sendTransaction(tx)
ret = await this.provider.send('eth_sendRawTransaction', [signedTx])
debug('eth_sendRawTransaction ret=', ret)
const resp = await this.signer.sendTransaction(tx)
const rcpt = await resp.wait()
ret = rcpt.transactionHash
// ret = await this.provider.send('eth_sendRawTransaction', [signedTx])
debug('eth_sendTransaction ret=', ret)
}
// TODO: parse ret, and revert if needed.
debug('ret=', ret)
@@ -125,11 +132,11 @@ export class BundleManager {
const userOp = userOps[opIndex]
const reasonStr: string = reason.toString()
if (reasonStr.startsWith('AA3')) {
this.reputationManager.crashedHandleOps(getAddr(userOp.paymasterAndData))
this.reputationManager.crashedHandleOps(userOp.paymaster)
} else if (reasonStr.startsWith('AA2')) {
this.reputationManager.crashedHandleOps(userOp.sender)
} else if (reasonStr.startsWith('AA1')) {
this.reputationManager.crashedHandleOps(getAddr(userOp.initCode))
this.reputationManager.crashedHandleOps(userOp.factory)
} else {
this.mempoolManager.removeUserOp(userOp)
console.warn(`Failed handleOps sender=${userOp.sender} reason=${reasonStr}`)
@@ -165,8 +172,8 @@ export class BundleManager {
// eslint-disable-next-line no-labels
mainLoop:
for (const entry of entries) {
const paymaster = getAddr(entry.userOp.paymasterAndData)
const factory = getAddr(entry.userOp.initCode)
const paymaster = entry.userOp.paymaster
const factory = entry.userOp.factory
const paymasterStatus = this.reputationManager.getStatus(paymaster)
const deployerStatus = this.reputationManager.getStatus(factory)
if (paymasterStatus === ReputationStatus.BANNED || deployerStatus === ReputationStatus.BANNED) {
@@ -202,7 +209,7 @@ export class BundleManager {
for (const storageAddress of Object.keys(validationResult.storageMap)) {
if (
storageAddress.toLowerCase() !== entry.userOp.sender.toLowerCase() &&
knownSenders.includes(storageAddress.toLowerCase())
knownSenders.includes(storageAddress.toLowerCase())
) {
console.debug(`UserOperation from ${entry.userOp.sender} sender accessed a storage of another known sender ${storageAddress}`)
// eslint-disable-next-line no-labels
@@ -236,7 +243,7 @@ export class BundleManager {
}
// If sender's account already exist: replace with its storage root hash
if (this.mergeToAccountRootHash && this.conditionalRpc && entry.userOp.initCode.length <= 2) {
if (this.mergeToAccountRootHash && this.conditionalRpc && entry.userOp.factory == null) {
const { storageHash } = await this.provider.send('eth_getProof', [entry.userOp.sender, [], 'latest'])
storageMap[entry.userOp.sender.toLowerCase()] = storageHash
}
@@ -268,7 +275,7 @@ export class BundleManager {
async getUserOpHashes (userOps: UserOperation[]): Promise<string[]> {
const { userOpHashes } = await runContractScript(this.entryPoint.provider,
new GetUserOpHashes__factory(),
[this.entryPoint.address, userOps])
[this.entryPoint.address, userOps.map(packUserOp)])
return userOpHashes
}

View File

@@ -1,10 +1,12 @@
import { AccountDeployedEvent, UserOperationEventEvent } from '@account-abstraction/contracts/dist/types/EntryPoint'
import { ReputationManager } from './ReputationManager'
import { EntryPoint } from '@account-abstraction/contracts'
import Debug from 'debug'
import { SignatureAggregatorChangedEvent } from '@account-abstraction/contracts/types/EntryPoint'
import { TypedEvent } from '@account-abstraction/contracts/dist/types/common'
import { MempoolManager } from './MempoolManager'
import { TypedEvent } from '../types/common'
import {
AccountDeployedEvent, IEntryPoint,
SignatureAggregatorChangedEvent,
UserOperationEventEvent
} from '@account-abstraction/utils'
const debug = Debug('aa.events')
@@ -15,7 +17,7 @@ export class EventsManager {
lastBlock?: number
constructor (
readonly entryPoint: EntryPoint,
readonly entryPoint: IEntryPoint,
readonly mempoolManager: MempoolManager,
readonly reputationManager: ReputationManager) {
}

View File

@@ -1,7 +1,7 @@
import Debug from 'debug'
import { Mutex } from 'async-mutex'
import { ValidationManager } from '@account-abstraction/validation-manager'
import { UserOperation } from '@account-abstraction/utils'
import { packUserOp, UserOperation } from '@account-abstraction/utils'
import { clearInterval } from 'timers'
import { BundleManager, SendBundleReturn } from './BundleManager'
@@ -37,7 +37,7 @@ export class ExecutionManager {
debug('sendUserOperation')
this.validationManager.validateInputParameters(userOp, entryPointInput)
const validationResult = await this.validationManager.validateUserOp(userOp, undefined)
const userOpHash = await this.validationManager.entryPoint.getUserOpHash(userOp)
const userOpHash = await this.validationManager.entryPoint.getUserOpHash(packUserOp(userOp))
this.mempoolManager.addUserOp(userOp,
userOpHash,
validationResult.returnInfo.prefund,

View File

@@ -5,7 +5,6 @@ import {
StakeInfo,
UserOperation,
ValidationErrors,
getAddr,
requireCond
} from '@account-abstraction/utils'
import { ReputationManager } from './ReputationManager'
@@ -67,7 +66,7 @@ export class MempoolManager {
// add userOp into the mempool, after initial validation.
// replace existing, if any (and if new gas is higher)
// revets if unable to add UserOp to mempool (too many UserOps with this sender)
// reverts if unable to add UserOp to mempool (too many UserOps with this sender)
addUserOp (
userOp: UserOperation,
userOpHash: string,
@@ -94,13 +93,11 @@ export class MempoolManager {
} else {
debug('add userOp', userOp.sender, userOp.nonce)
this.incrementEntryCount(userOp.sender)
const paymaster = getAddr(userOp.paymasterAndData)
if (paymaster != null) {
this.incrementEntryCount(paymaster)
if (userOp.paymaster != null) {
this.incrementEntryCount(userOp.paymaster)
}
const factory = getAddr(userOp.initCode)
if (factory != null) {
this.incrementEntryCount(factory)
if (userOp.factory != null) {
this.incrementEntryCount(userOp.factory)
}
this.checkReputation(senderInfo, paymasterInfo, factoryInfo, aggregatorInfo)
this.checkMultipleRolesViolation(userOp)
@@ -117,8 +114,8 @@ export class MempoolManager {
if (!(e instanceof RpcError)) throw e
}
this.reputationManager.updateSeenStatus(aggregator)
this.reputationManager.updateSeenStatus(getAddr(userOp.paymasterAndData))
this.reputationManager.updateSeenStatus(getAddr(userOp.initCode))
this.reputationManager.updateSeenStatus(userOp.paymaster)
this.reputationManager.updateSeenStatus(userOp.factory)
}
// TODO: de-duplicate code
@@ -152,20 +149,20 @@ export class MempoolManager {
)
const knownSenders = this.getKnownSenders()
const paymaster = getAddr(userOp.paymasterAndData)?.toLowerCase()
const factory = getAddr(userOp.initCode)?.toLowerCase()
const paymaster = userOp.paymaster
const factory = userOp.factory
const isPaymasterSenderViolation = knownSenders.includes(paymaster?.toLowerCase() ?? '')
const isFactorySenderViolation = knownSenders.includes(factory?.toLowerCase() ?? '')
requireCond(
!isPaymasterSenderViolation,
`A Paymaster at ${paymaster} in this UserOperation is used as a sender entity in another UserOperation currently in mempool.`,
`A Paymaster at ${paymaster as string} in this UserOperation is used as a sender entity in another UserOperation currently in mempool.`,
ValidationErrors.OpcodeValidation
)
requireCond(
!isFactorySenderViolation,
`A Factory at ${factory} in this UserOperation is used as a sender entity in another UserOperation currently in mempool.`,
`A Factory at ${factory as string} in this UserOperation is used as a sender entity in another UserOperation currently in mempool.`,
ValidationErrors.OpcodeValidation
)
}
@@ -247,8 +244,8 @@ export class MempoolManager {
debug('removeUserOp', userOp.sender, userOp.nonce)
this.mempool.splice(index, 1)
this.decrementEntryCount(userOp.sender)
this.decrementEntryCount(getAddr(userOp.paymasterAndData))
this.decrementEntryCount(getAddr(userOp.initCode))
this.decrementEntryCount(userOp.paymaster)
this.decrementEntryCount(userOp.factory)
// TODO: store and remove aggregator entity count
}
}
@@ -285,15 +282,12 @@ export class MempoolManager {
const res = []
const userOps = this.mempool
res.push(
...userOps.map(it => {
return getAddr(it.userOp.paymasterAndData)
})
...userOps.map(it => it.userOp.paymaster)
)
res.push(
...userOps.map(it => {
return getAddr(it.userOp.initCode)
})
...userOps.map(it => it.userOp.factory)
)
return res.filter(it => it != null).map(it => (it as string).toLowerCase())
}
}

View File

@@ -3,12 +3,12 @@ import { BundlerReputationParams, ReputationManager } from './ReputationManager'
import { MempoolManager } from './MempoolManager'
import { BundleManager } from './BundleManager'
import { ValidationManager } from '@account-abstraction/validation-manager'
import { EntryPoint__factory } from '@account-abstraction/contracts'
import { parseEther } from 'ethers/lib/utils'
import { Signer } from 'ethers'
import { BundlerConfig } from '../BundlerConfig'
import { EventsManager } from './EventsManager'
import { getNetworkProvider } from '../Config'
import { IEntryPoint__factory } from '@account-abstraction/utils'
/**
* initialize server modules.
@@ -17,7 +17,7 @@ import { getNetworkProvider } from '../Config'
* @param signer
*/
export function initServer (config: BundlerConfig, signer: Signer): [ExecutionManager, EventsManager, ReputationManager, MempoolManager] {
const entryPoint = EntryPoint__factory.connect(config.entryPoint, signer)
const entryPoint = IEntryPoint__factory.connect(config.entryPoint, signer)
const reputationManager = new ReputationManager(getNetworkProvider(config.network), BundlerReputationParams, parseEther(config.minStake), config.minUnstakeDelay)
const mempoolManager = new MempoolManager(reputationManager)
const validationManager = new ValidationManager(entryPoint, config.unsafe)

View File

@@ -1,16 +1,20 @@
import fs from 'fs'
import { Command } from 'commander'
import { erc4337RuntimeVersion, RpcError, supportsRpcMethod } from '@account-abstraction/utils'
import {
deployEntryPoint,
erc4337RuntimeVersion,
IEntryPoint,
RpcError,
supportsRpcMethod
} from '@account-abstraction/utils'
import { ethers, Wallet, Signer } from 'ethers'
import { BundlerServer } from './BundlerServer'
import { UserOpMethodHandler } from './UserOpMethodHandler'
import { EntryPoint, EntryPoint__factory } from '@account-abstraction/contracts'
import { initServer } from './modules/initServer'
import { DebugMethodHandler } from './DebugMethodHandler'
import { DeterministicDeployer } from '@account-abstraction/sdk'
import { supportsDebugTraceCall } from '@account-abstraction/validation-manager'
import { resolveConfiguration } from './Config'
import { bundlerConfigDefault } from './BundlerConfig'
@@ -30,8 +34,8 @@ export let showStackTraces = false
export async function connectContracts (
wallet: Signer,
entryPointAddress: string): Promise<{ entryPoint: EntryPoint }> {
const entryPoint = EntryPoint__factory.connect(entryPointAddress, wallet)
entryPointAddress: string): Promise<{ entryPoint: IEntryPoint }> {
const entryPoint = await deployEntryPoint(wallet.provider as any, wallet as any)
return {
entryPoint
}
@@ -105,7 +109,9 @@ export async function runBundler (argv: string[], overrideExit = true): Promise<
} else {
console.log('== debugrpc already st', config.debugRpc)
}
await new DeterministicDeployer(provider as any).deterministicDeploy(EntryPoint__factory.bytecode)
const ep = await deployEntryPoint(provider as any)
const addr = ep.address
console.log('deployed EntryPoint at', addr)
if ((await wallet.getBalance()).eq(0)) {
console.log('=== testnet: fund signer')
const signer = (provider as JsonRpcProvider).getSigner()

View File

@@ -7,17 +7,16 @@
import { BigNumber, Signer, Wallet } from 'ethers'
import { JsonRpcProvider } from '@ethersproject/providers'
import { SimpleAccountFactory__factory } from '@account-abstraction/contracts'
import { formatEther, keccak256, parseEther } from 'ethers/lib/utils'
import { Command } from 'commander'
import { erc4337RuntimeVersion } from '@account-abstraction/utils'
import { DeterministicDeployer, erc4337RuntimeVersion, SimpleAccountFactory__factory } from '@account-abstraction/utils'
import fs from 'fs'
import { DeterministicDeployer, HttpRpcClient, SimpleAccountAPI } from '@account-abstraction/sdk'
import { HttpRpcClient, SimpleAccountAPI } from '@account-abstraction/sdk'
import { runBundler } from '../runBundler'
import { BundlerServer } from '../BundlerServer'
import { getNetworkProvider } from '../Config'
const ENTRY_POINT = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
const ENTRY_POINT = '0x0000000071727De22E5E9d8BAf0edAc6f37da032'
class Runner {
bundlerProvider!: HttpRpcClient

View File

@@ -1,17 +1,20 @@
import { EntryPoint, EntryPoint__factory } from '@account-abstraction/contracts'
import { parseEther } from 'ethers/lib/utils'
import { assert, expect } from 'chai'
import { BundlerReputationParams, ReputationManager } from '../src/modules/ReputationManager'
import { AddressZero, getUserOpHash } from '@account-abstraction/utils'
import {
AddressZero,
getUserOpHash,
packUserOp,
UserOperation,
deployEntryPoint, IEntryPoint, DeterministicDeployer
} from '@account-abstraction/utils'
import { ValidationManager, supportsDebugTraceCall } from '@account-abstraction/validation-manager'
import { DeterministicDeployer } from '@account-abstraction/sdk'
import { MempoolManager } from '../src/modules/MempoolManager'
import { BundleManager } from '../src/modules/BundleManager'
import { ethers } from 'hardhat'
import { BundlerConfig } from '../src/BundlerConfig'
import { TestFakeWalletToken__factory } from '../src/types'
import { UserOperation } from '../src/modules/Types'
import { UserOpMethodHandler } from '../src/UserOpMethodHandler'
import { ExecutionManager } from '../src/modules/ExecutionManager'
import { EventsManager } from '../src/modules/EventsManager'
@@ -20,13 +23,13 @@ import { createSigner } from './testUtils'
describe('#BundlerManager', () => {
let bm: BundleManager
let entryPoint: EntryPoint
let entryPoint: IEntryPoint
const provider = ethers.provider
const signer = provider.getSigner()
before(async function () {
entryPoint = await new EntryPoint__factory(signer).deploy()
entryPoint = await deployEntryPoint(provider)
DeterministicDeployer.init(provider)
const config: BundlerConfig = {
@@ -43,22 +46,22 @@ describe('#BundlerManager', () => {
maxBundleGas: 5e6,
// minstake zero, since we don't fund deployer.
minStake: '0',
minUnstakeDelay: 0
minUnstakeDelay: 0,
conditionalRpc: false
}
const repMgr = new ReputationManager(provider, BundlerReputationParams, parseEther(config.minStake), config.minUnstakeDelay)
const mempoolMgr = new MempoolManager(repMgr)
const validMgr = new ValidationManager(entryPoint, repMgr, config.unsafe)
bm = new BundleManager(entryPoint, mempoolMgr, validMgr, repMgr, config.beneficiary, parseEther(config.minBalance), config.maxBundleGas)
const validMgr = new ValidationManager(entryPoint, config.unsafe)
const evMgr = new EventsManager(entryPoint, mempoolMgr, repMgr)
bm = new BundleManager(entryPoint, evMgr, mempoolMgr, validMgr, repMgr, config.beneficiary, parseEther(config.minBalance), config.maxBundleGas, config.conditionalRpc)
})
it('#getUserOpHashes', async () => {
const userOp: UserOperation = {
sender: AddressZero,
nonce: 1,
paymasterAndData: '0x02',
signature: '0x03',
initCode: '0x04',
callData: '0x05',
callGasLimit: 6,
verificationGasLimit: 7,
@@ -67,7 +70,7 @@ describe('#BundlerManager', () => {
preVerificationGas: 10
}
const hash = await entryPoint.getUserOpHash(userOp)
const hash = await entryPoint.getUserOpHash(packUserOp(userOp))
const bmHash = await bm.getUserOpHashes([userOp])
expect(bmHash).to.eql([hash])
})
@@ -98,7 +101,7 @@ describe('#BundlerManager', () => {
}
const repMgr = new ReputationManager(provider, BundlerReputationParams, parseEther(config.minStake), config.minUnstakeDelay)
const mempoolMgr = new MempoolManager(repMgr)
const validMgr = new ValidationManager(_entryPoint, repMgr, config.unsafe)
const validMgr = new ValidationManager(_entryPoint, config.unsafe)
const evMgr = new EventsManager(_entryPoint, mempoolMgr, repMgr)
bundleMgr = new BundleManager(_entryPoint, evMgr, mempoolMgr, validMgr, repMgr, config.beneficiary, parseEther(config.minBalance), config.maxBundleGas, false)
const execManager = new ExecutionManager(repMgr, mempoolMgr, bundleMgr, validMgr)
@@ -131,9 +134,7 @@ describe('#BundlerManager', () => {
const cEmptyUserOp: UserOperation = {
sender: AddressZero,
nonce: '0x0',
paymasterAndData: '0x',
signature: '0x',
initCode: '0x',
callData: '0x',
callGasLimit: '0x0',
verificationGasLimit: '0x50000',

View File

@@ -1,3 +1,110 @@
import {
AddressZero,
deepHexlify,
deployEntryPoint, IEntryPoint,
UserOperation
} from '@account-abstraction/utils'
import { BundlerServer } from '../src/BundlerServer'
import { expect } from 'chai'
import { createSigner } from './testUtils'
import { BundlerReputationParams, ReputationManager } from '../src/modules/ReputationManager'
import { parseEther } from 'ethers/lib/utils'
import { MempoolManager } from '../src/modules/MempoolManager'
import { supportsDebugTraceCall, ValidationManager } from '@account-abstraction/validation-manager'
import { EventsManager } from '../src/modules/EventsManager'
import { BundleManager } from '../src/modules/BundleManager'
import { ExecutionManager } from '../src/modules/ExecutionManager'
import { UserOpMethodHandler } from '../src/UserOpMethodHandler'
import { ethers } from 'hardhat'
import { BundlerConfig } from '../src/BundlerConfig'
describe('BundleServer', function () {
// it('preflightCheck')
let entryPoint: IEntryPoint
let server: BundlerServer
before(async () => {
const provider = ethers.provider
const signer = await createSigner()
entryPoint = await deployEntryPoint(provider)
const config: BundlerConfig = {
beneficiary: await signer.getAddress(),
entryPoint: entryPoint.address,
gasFactor: '0.2',
minBalance: '0',
mnemonic: '',
network: '',
port: '3000',
unsafe: !await supportsDebugTraceCall(provider as any),
conditionalRpc: false,
autoBundleInterval: 0,
autoBundleMempoolSize: 0,
maxBundleGas: 5e6,
// minstake zero, since we don't fund deployer.
minStake: '0',
minUnstakeDelay: 0
}
const repMgr = new ReputationManager(provider, BundlerReputationParams, parseEther(config.minStake), config.minUnstakeDelay)
const mempoolMgr = new MempoolManager(repMgr)
const validMgr = new ValidationManager(entryPoint, config.unsafe)
const evMgr = new EventsManager(entryPoint, mempoolMgr, repMgr)
const bundleMgr = new BundleManager(entryPoint, evMgr, mempoolMgr, validMgr, repMgr, config.beneficiary, parseEther(config.minBalance), config.maxBundleGas, false)
const execManager = new ExecutionManager(repMgr, mempoolMgr, bundleMgr, validMgr)
const methodHandler = new UserOpMethodHandler(
execManager,
provider,
signer,
config,
entryPoint
)
const None: any = {}
server = new BundlerServer(methodHandler, None, None, None, None)
server.silent = true
})
it('should revert on invalid userop', async () => {
const op = {}
expect(await server.handleRpc({
id: 1,
jsonrpc: '2.0',
method: 'eth_sendUserOperation',
params: [op, entryPoint.address]
})).to.eql({
id: 1,
jsonrpc: '2.0',
error: {
code: -32602,
data: undefined,
message: 'Missing userOp field: sender'
}
})
})
it('should return bundler error', async () => {
const op: UserOperation = deepHexlify({
sender: AddressZero,
nonce: '0x1',
callData: '0x',
callGasLimit: 1e6,
verificationGasLimit: 1e6,
preVerificationGas: 50000,
maxFeePerGas: 1e6,
maxPriorityFeePerGas: 1e6,
signature: '0x'
})
expect(await server.handleRpc({
id: 1,
jsonrpc: '2.0',
method: 'eth_sendUserOperation',
params: [op, entryPoint.address]
})).to.eql({
id: 1,
jsonrpc: '2.0',
error: {
code: -32500,
data: undefined,
message: 'FailedOp(0,"AA20 account not deployed")'
}
})
})
})

View File

@@ -8,10 +8,13 @@ import { ValidationManager, supportsDebugTraceCall } from '@account-abstraction/
import { BundleManager, SendBundleReturn } from '../src/modules/BundleManager'
import { UserOpMethodHandler } from '../src/UserOpMethodHandler'
import { ethers } from 'hardhat'
import { EntryPoint, EntryPoint__factory, SimpleAccountFactory__factory } from '@account-abstraction/contracts'
import { DeterministicDeployer, SimpleAccountAPI } from '@account-abstraction/sdk'
import { SimpleAccountAPI } from '@account-abstraction/sdk'
import { Signer, Wallet } from 'ethers'
import { resolveHexlify } from '@account-abstraction/utils'
import {
IEntryPoint,
resolveHexlify,
SimpleAccountFactory__factory, deployEntryPoint, DeterministicDeployer
} from '@account-abstraction/utils'
import { expect } from 'chai'
import { createSigner } from './testUtils'
import { EventsManager } from '../src/modules/EventsManager'
@@ -20,7 +23,7 @@ const provider = ethers.provider
describe('#DebugMethodHandler', () => {
let debugMethodHandler: DebugMethodHandler
let entryPoint: EntryPoint
let entryPoint: IEntryPoint
let methodHandler: UserOpMethodHandler
let smartAccountAPI: SimpleAccountAPI
let signer: Signer
@@ -29,7 +32,7 @@ describe('#DebugMethodHandler', () => {
before(async () => {
signer = await createSigner()
entryPoint = await new EntryPoint__factory(signer).deploy()
entryPoint = await deployEntryPoint(provider)
DeterministicDeployer.init(provider)
const config: BundlerConfig = {
@@ -52,7 +55,7 @@ describe('#DebugMethodHandler', () => {
const repMgr = new ReputationManager(provider, BundlerReputationParams, parseEther(config.minStake), config.minUnstakeDelay)
const mempoolMgr = new MempoolManager(repMgr)
const validMgr = new ValidationManager(entryPoint, repMgr, config.unsafe)
const validMgr = new ValidationManager(entryPoint, config.unsafe)
const eventsManager = new EventsManager(entryPoint, mempoolMgr, repMgr)
const bundleMgr = new BundleManager(entryPoint, eventsManager, mempoolMgr, validMgr, repMgr,
config.beneficiary, parseEther(config.minBalance), config.maxBundleGas, false)

View File

@@ -3,15 +3,9 @@ import { assert, expect } from 'chai'
import { parseEther, resolveProperties } from 'ethers/lib/utils'
import { BundlerConfig } from '../src/BundlerConfig'
import {
EntryPoint,
EntryPoint__factory,
SimpleAccountFactory__factory,
UserOperationStruct
} from '@account-abstraction/contracts'
import { Signer, Wallet } from 'ethers'
import { DeterministicDeployer, SimpleAccountAPI } from '@account-abstraction/sdk'
import { SimpleAccountAPI } from '@account-abstraction/sdk'
import { postExecutionDump } from '@account-abstraction/utils/dist/src/postExecCheck'
import {
SampleRecipient,
@@ -19,8 +13,16 @@ import {
TestRulesAccount__factory
} from '../src/types'
import { ValidationManager, supportsDebugTraceCall } from '@account-abstraction/validation-manager'
import { resolveHexlify, waitFor } from '@account-abstraction/utils'
import { UserOperationEventEvent } from '@account-abstraction/contracts/dist/types/EntryPoint'
import {
deployEntryPoint,
DeterministicDeployer,
IEntryPoint,
packUserOp,
resolveHexlify,
SimpleAccountFactory__factory,
UserOperation,
waitFor
} from '@account-abstraction/utils'
import { UserOperationReceipt } from '../src/RpcTypes'
import { ExecutionManager } from '../src/modules/ExecutionManager'
import { BundlerReputationParams, ReputationManager } from '../src/modules/ReputationManager'
@@ -28,7 +30,7 @@ import { MempoolManager } from '../src/modules/MempoolManager'
import { BundleManager } from '../src/modules/BundleManager'
import { UserOpMethodHandler } from '../src/UserOpMethodHandler'
import { ethers } from 'hardhat'
import { createSigner, findMin } from './testUtils'
import { createSigner } from './testUtils'
import { EventsManager } from '../src/modules/EventsManager'
describe('UserOpMethodHandler', function () {
@@ -41,16 +43,16 @@ describe('UserOpMethodHandler', function () {
const accountSigner = Wallet.createRandom()
let mempoolMgr: MempoolManager
let entryPoint: EntryPoint
let entryPoint: IEntryPoint
let sampleRecipient: SampleRecipient
before(async function () {
provider = ethers.provider
DeterministicDeployer.init(ethers.provider)
signer = await createSigner()
entryPoint = await new EntryPoint__factory(signer).deploy()
entryPoint = await deployEntryPoint(ethers.provider, ethers.provider.getSigner())
DeterministicDeployer.init(ethers.provider)
accountDeployerAddress = await DeterministicDeployer.deploy(new SimpleAccountFactory__factory(), 0, [entryPoint.address])
const sampleRecipientFactory = await ethers.getContractFactory('SampleRecipient')
@@ -115,7 +117,7 @@ describe('UserOpMethodHandler', function () {
target,
data: '0xdeadface'
})
expect(await methodHandler.estimateUserOperationGas(await resolveHexlify(op), entryPoint.address).catch(e => e.message)).to.eql('AA21 didn\'t pay prefund')
expect(await methodHandler.estimateUserOperationGas(await resolveHexlify(op), entryPoint.address).catch(e => e.message)).to.match(/AA21 didn't pay prefund/)
// should estimate with gasprice=0
const op1 = await smartAccountAPI.createSignedUserOp({
maxFeePerGas: 0,
@@ -133,7 +135,7 @@ describe('UserOpMethodHandler', function () {
})
describe('sendUserOperation', function () {
let userOperation: UserOperationStruct
let userOperation: UserOperation
let accountAddress: string
let accountDeployerAddress: string
@@ -165,7 +167,7 @@ describe('UserOpMethodHandler', function () {
// sendUserOperation is async, even in auto-mining. need to wait for it.
const event = await waitFor(async () => await entryPoint.queryFilter(entryPoint.filters.UserOperationEvent(userOpHash)).then(ret => ret?.[0]))
const transactionReceipt = await event!.getTransactionReceipt()
const transactionReceipt = await event.getTransactionReceipt()
assert.isNotNull(transactionReceipt)
const logs = transactionReceipt.logs.filter(log => log.address === entryPoint.address)
.map(log => entryPoint.interface.parseLog(log))
@@ -179,9 +181,6 @@ describe('UserOpMethodHandler', function () {
const userOperationEvent = logs[3]
assert.equal(userOperationEvent.args.success, true)
const expectedTxOrigin = await methodHandler.signer.getAddress()
assert.equal(senderEvent.args.txOrigin, expectedTxOrigin, 'sample origin should be bundler')
assert.equal(senderEvent.args.msgSender, accountAddress, 'sample msgsender should be account address')
})
@@ -258,58 +257,6 @@ describe('UserOpMethodHandler', function () {
})
})
describe('check aa51', function () {
const ethersSigner = ethers.provider.getSigner()
it('should return bundler error', async function () {
this.timeout(200000)
const privkey = '0x'.padEnd(66, '9')
const owner = new Wallet(privkey)
const factory = await new SimpleAccountFactory__factory(ethersSigner).deploy(entryPoint.address)
const accountAddress = await factory.getAddress(owner.address, 0)
await factory.createAccount(owner.address, 0)
const acc = new SimpleAccountAPI({
provider: ethers.provider,
entryPointAddress: entryPoint.address,
owner,
accountAddress
})
await ethersSigner.sendTransaction({ to: await acc.getAccountAddress(), value: parseEther('1') })
// find the largest "reverting" verificationGasLimit, and try to submit it to the bundler:
async function createTestUserOpWithVerGas (n: number): Promise<boolean> {
const op = await acc.createUnsignedUserOp({ target: factory.address, data: '0xdead', gasLimit: 1000, nonce: 0 })
op.verificationGasLimit = n + 1
op.maxFeePerGas = op.maxPriorityFeePerGas = 1
const signed = await acc.signUserOp(op)
try {
await entryPoint.callStatic.handleOps([signed], entryPoint.address)
// console.log(n, 'ok')
return true
} catch (e) {
// const data = e.data
// console.log(n, 'ex', typeof data !== 'string' ? e.message : decodeErrorReason(data)?.message)
return false
}
}
const verGL = await findMin(createTestUserOpWithVerGas, 10000, 500000, 2)
const op = await resolveProperties(await acc.createUnsignedUserOp({
target: factory.address,
data: '0xdead',
nonce: 0,
gasLimit: 1000 // deliberately too little
})) as UserOperationStruct
op.maxFeePerGas = op.maxPriorityFeePerGas = 1
op.verificationGasLimit = verGL // this is a value that handleOps reverts
await expect(methodHandler.sendUserOperation(await resolveHexlify(await acc.signUserOp(op)), entryPoint.address))
.to.rejectedWith('verificationGas should have extra')
op.verificationGasLimit = verGL + 1200
await methodHandler.sendUserOperation(await resolveHexlify(await acc.signUserOp(op)), entryPoint.address)
})
})
describe('#_filterLogs', function () {
// test events, good enough for _filterLogs
function userOpEv (hash: any): any {
@@ -352,9 +299,8 @@ describe('UserOpMethodHandler', function () {
acc = await new TestRulesAccount__factory(signer).deploy()
const callData = acc.interface.encodeFunctionData('execSendMessage')
const op: UserOperationStruct = {
const op: UserOperation = {
sender: acc.address,
initCode: '0x',
nonce: 0,
callData,
callGasLimit: 1e6,
@@ -362,14 +308,13 @@ describe('UserOpMethodHandler', function () {
preVerificationGas: 50000,
maxFeePerGas: 1e6,
maxPriorityFeePerGas: 1e6,
paymasterAndData: '0x',
signature: Buffer.from('emit-msg')
}
await entryPoint.depositTo(acc.address, { value: parseEther('1') })
// await signer.sendTransaction({to:acc.address, value: parseEther('1')})
userOpHash = await entryPoint.getUserOpHash(op)
userOpHash = await entryPoint.getUserOpHash(packUserOp(op))
const beneficiary = signer.getAddress()
await entryPoint.handleOps([op], beneficiary).then(async ret => await ret.wait())
await entryPoint.handleOps([packUserOp(op)], beneficiary).then(async ret => await ret.wait())
const rcpt = await methodHandler.getUserOperationReceipt(userOpHash)
if (rcpt == null) {
throw new Error('getUserOperationReceipt returns null')

View File

@@ -1,9 +1,14 @@
import { EntryPoint, EntryPoint__factory } from '@account-abstraction/contracts'
import { assert, expect } from 'chai'
import { defaultAbiCoder, hexConcat, hexlify, keccak256, parseEther } from 'ethers/lib/utils'
import { ethers } from 'hardhat'
import { AddressZero, decodeErrorReason, toBytes32 } from '@account-abstraction/utils'
import {
AddressZero,
decodeErrorReason, deployEntryPoint,
IEntryPoint,
toBytes32,
UserOperation
} from '@account-abstraction/utils'
import {
ValidateUserOpResult,
ValidationManager,
@@ -29,16 +34,11 @@ import {
TestTimeRangeAccountFactory,
TestTimeRangeAccountFactory__factory
} from '../src/types'
import { ReputationManager } from '../src/modules/ReputationManager'
import { UserOperation } from '../src/modules/Types'
const cEmptyUserOp: UserOperation = {
sender: AddressZero,
nonce: 0,
paymasterAndData: '0x',
signature: '0x',
initCode: '0x',
callData: '0x',
callGasLimit: 0,
verificationGasLimit: 50000,
@@ -54,7 +54,7 @@ describe('#ValidationManager', () => {
let testcoin: TestCoin
let paymaster: TestOpcodesAccount
let entryPoint: EntryPoint
let entryPoint: IEntryPoint
let rulesAccount: TestRulesAccount
let storageAccount: TestStorageAccount
@@ -69,16 +69,23 @@ describe('#ValidationManager', () => {
}
async function existingStorageAccountUserOp (validateRule = '', pmRule = ''): Promise<UserOperation> {
const paymasterAndData = pmRule === '' ? '0x' : hexConcat([paymaster.address, Buffer.from(pmRule)])
const pmd = pmRule === ''
? {}
: {
paymaster: paymaster.address,
paymasterVerificationGasLimit: 1e5,
paymasterPostOpGasLimit: 1e5,
paymasterData: Buffer.from(pmRule)
}
const signature = hexlify(Buffer.from(validateRule))
return {
...cEmptyUserOp,
sender: storageAccount.address,
signature,
paymasterAndData,
callGasLimit: 1e6,
verificationGasLimit: 1e6,
preVerificationGas: 50000
preVerificationGas: 50000,
pmd
}
}
@@ -87,11 +94,14 @@ describe('#ValidationManager', () => {
initFunc = opcodeFactory.interface.encodeFunctionData('create', [''])
}
const initCode = hexConcat([
factoryAddress,
initFunc
])
const paymasterAndData = pmRule == null ? '0x' : hexConcat([paymaster.address, Buffer.from(pmRule)])
const pmInfo = pmRule == null
? {}
: {
paymaster: paymaster.address,
paymasterVerificationGasLimit: 1e6,
paymasterPostOpGasLimit: 1e6,
paymasterData: Buffer.from(pmRule)
}
const signature = hexlify(Buffer.from(validateRule))
const callinitCodeForAddr = await provider.call({
to: factoryAddress,
@@ -102,23 +112,25 @@ describe('#ValidationManager', () => {
throw new Error(decodeErrorReason(callinitCodeForAddr)?.message)
}
const [sender] = defaultAbiCoder.decode(['address'], callinitCodeForAddr)
return {
const op: UserOperation = {
...cEmptyUserOp,
sender,
initCode,
signature,
paymasterAndData,
callGasLimit: 1e6,
verificationGasLimit: 1e6,
preVerificationGas: 50000
preVerificationGas: 50000,
factory: factoryAddress,
factoryData: initFunc,
...pmInfo
}
return op
}
const provider = ethers.provider
const ethersSigner = provider.getSigner()
before(async function () {
entryPoint = await new EntryPoint__factory(ethersSigner).deploy()
entryPoint = await deployEntryPoint(provider)
paymaster = await new TestOpcodesAccount__factory(ethersSigner).deploy()
await entryPoint.depositTo(paymaster.address, { value: parseEther('0.1') })
await paymaster.addStake(entryPoint.address, { value: parseEther('0.1') })
@@ -134,14 +146,8 @@ describe('#ValidationManager', () => {
await rulesFactory.create('')
await entryPoint.depositTo(rulesAccount.address, { value: parseEther('1') })
const reputationManager = new ReputationManager(provider, {
minInclusionDenominator: 1,
throttlingSlack: 1,
banSlack: 1
},
parseEther('0'), 0)
const unsafe = !await supportsDebugTraceCall(provider)
vm = new ValidationManager(entryPoint, reputationManager, unsafe)
vm = new ValidationManager(entryPoint, unsafe)
if (!await supportsDebugTraceCall(ethers.provider)) {
console.log('WARNING: opcode banning tests can only run with geth')
@@ -200,7 +206,7 @@ describe('#ValidationManager', () => {
it('account fails to read allowance of other address (even if account is token owner)', async () => {
expect(await testExistingUserOp('allowance-self-1')
.catch(e => e.message))
.to.match(/account has forbidden read/)
.to.match(/unstaked account accessed/)
})
it('account can reference its own allowance on other contract balance', async () => {
await testExistingUserOp('allowance-1-self')
@@ -214,7 +220,7 @@ describe('#ValidationManager', () => {
it('should fail to access other address struct data', async () => {
expect(await testExistingUserOp('struct-1')
.catch(e => e.message)
).match(/account has forbidden read/)
).match(/unstaked account accessed/)
})
})
@@ -226,7 +232,6 @@ describe('#ValidationManager', () => {
const userOp = await createTestUserOp('', undefined, undefined, testTimeRangeAccountFactory.address)
userOp.preVerificationGas = Math.floor(validAfterMs / 1000)
userOp.maxPriorityFeePerGas = Math.floor(validUntilMs / 1000)
console.log('=== validAfter: ', userOp.preVerificationGas, 'validuntil', userOp.maxPriorityFeePerGas)
await vm.validateUserOp(userOp)
}
@@ -319,7 +324,7 @@ describe('#ValidationManager', () => {
it('should fail if referencing other token balance', async () => {
expect(await testUserOp('balance-1', undefined, storageFactory.interface.encodeFunctionData('create', [0, '']), storageFactory.address)
.catch(e => e.message))
.to.match(/account has forbidden read/)
.to.match(/unstaked account accessed/)
})
it('should succeed referencing self token balance after wallet creation', async () => {
@@ -332,13 +337,13 @@ describe('#ValidationManager', () => {
// await pm.addStake(entryPoint.address, { value: parseEther('0.1') })
const acct = await new TestRecursionAccount__factory(ethersSigner).deploy(entryPoint.address)
const userOp = {
const userOp: UserOperation = {
...cEmptyUserOp,
sender: acct.address,
paymasterAndData: hexConcat([
pm.address,
Buffer.from('postOp-context')
])
paymaster: pm.address,
paymasterVerificationGasLimit: 1e6,
paymasterPostOpGasLimit: 1e6,
paymasterData: Buffer.from('postOp-context')
}
expect(await vm.validateUserOp(userOp)
.then(() => 'should fail', e => e.message))

View File

@@ -5,7 +5,7 @@ import { HardhatUserConfig } from 'hardhat/config'
const config: HardhatUserConfig = {
solidity: {
version: '0.8.15',
version: '0.8.23',
settings: {
optimizer: { enabled: true }
}

View File

@@ -1,6 +1,6 @@
{
"name": "@account-abstraction/sdk",
"version": "0.6.0",
"version": "0.7.0",
"main": "./dist/src/index.js",
"license": "MIT",
"files": [
@@ -18,9 +18,7 @@
"watch-tsc": "tsc -w --preserveWatchOutput"
},
"dependencies": {
"@account-abstraction/contracts": "^0.6.0",
"@account-abstraction/utils": "^0.6.0",
"@ethersproject/abstract-provider": "^5.7.0",
"@account-abstraction/utils": "^0.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/networks": "^5.7.0",
"@ethersproject/properties": "^5.7.0",
@@ -34,6 +32,6 @@
"@nomicfoundation/hardhat-toolbox": "^1.0.2",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"chai": "^4.3.6",
"hardhat": "^2.11.0"
"hardhat": "^2.17.0"
}
}

View File

@@ -1,16 +1,17 @@
import { ethers, BigNumber, BigNumberish } from 'ethers'
import { ethers, BigNumber, BigNumberish, BytesLike } from 'ethers'
import { Provider } from '@ethersproject/providers'
import {
EntryPoint, EntryPoint__factory,
UserOperationStruct
} from '@account-abstraction/contracts'
import { TransactionDetailsForUserOp } from './TransactionDetailsForUserOp'
import { resolveProperties } from 'ethers/lib/utils'
import { defaultAbiCoder } from 'ethers/lib/utils'
import { PaymasterAPI } from './PaymasterAPI'
import { getUserOpHash, NotPromise, packUserOp } from '@account-abstraction/utils'
import { encodeUserOp, getUserOpHash, IEntryPoint, IEntryPoint__factory, UserOperation } from '@account-abstraction/utils'
import { calcPreVerificationGas, GasOverheads } from './calcPreVerificationGas'
export interface FactoryParams {
factory: string
factoryData?: BytesLike
}
export interface BaseApiParams {
provider: Provider
entryPointAddress: string
@@ -41,7 +42,7 @@ export abstract class BaseAccountAPI {
private senderAddress!: string
private isPhantom = true
// entryPoint connected to "zero" address. allowed to make static calls (e.g. to getSenderAddress)
private readonly entryPointView: EntryPoint
private readonly entryPointView: IEntryPoint
provider: Provider
overheads?: Partial<GasOverheads>
@@ -61,7 +62,7 @@ export abstract class BaseAccountAPI {
this.paymasterAPI = params.paymasterAPI
// factory "connect" define the contract address. the contract "connect" defines the "from" address.
this.entryPointView = EntryPoint__factory.connect(params.entryPointAddress, params.provider).connect(ethers.constants.AddressZero)
this.entryPointView = IEntryPoint__factory.connect(params.entryPointAddress, params.provider).connect(ethers.constants.AddressZero)
}
async init (): Promise<this> {
@@ -74,10 +75,9 @@ export abstract class BaseAccountAPI {
}
/**
* return the value to put into the "initCode" field, if the contract is not yet deployed.
* this value holds the "factory" address, followed by this account's information
* return the value to put into the "factory" and "factoryData", when the contract is not yet deployed.
*/
abstract getAccountInitCode (): Promise<string>
abstract getFactoryData (): Promise<FactoryParams | null>
/**
* return current account's nonce.
@@ -120,29 +120,28 @@ export abstract class BaseAccountAPI {
* calculate the account address even before it is deployed
*/
async getCounterFactualAddress (): Promise<string> {
const initCode = this.getAccountInitCode()
const { factory, factoryData } = await this.getFactoryData() ?? {}
if (factory == null) {
throw new Error(('no counter factual address if not factory'))
}
// use entryPoint to query account address (factory can provide a helper method to do the same, but
// this method attempts to be generic
try {
await this.entryPointView.callStatic.getSenderAddress(initCode)
} catch (e: any) {
if (e.errorArgs == null) {
throw e
}
return e.errorArgs.sender
}
throw new Error('must handle revert')
const retAddr = await this.provider.call({
to: factory, data: factoryData
})
const [addr] = defaultAbiCoder.decode(['address'], retAddr)
return addr
}
/**
* return initCode value to into the UserOp.
* (either deployment code, or empty hex if contract already deployed)
* (either factory and factoryData, or null hex if contract already deployed)
*/
async getInitCode (): Promise<string> {
async getRequiredFactoryData (): Promise<FactoryParams | null> {
if (await this.checkAccountPhantom()) {
return await this.getAccountInitCode()
return await this.getFactoryData()
}
return '0x'
return null
}
/**
@@ -157,16 +156,15 @@ export abstract class BaseAccountAPI {
* should cover cost of putting calldata on-chain, and some overhead.
* actual overhead depends on the expected bundle size
*/
async getPreVerificationGas (userOp: Partial<UserOperationStruct>): Promise<number> {
const p = await resolveProperties(userOp)
return calcPreVerificationGas(p, this.overheads)
async getPreVerificationGas (userOp: Partial<UserOperation>): Promise<number> {
return calcPreVerificationGas(userOp, this.overheads)
}
/**
* ABI-encode a user operation. used for calldata cost estimation
*/
packUserOp (userOp: NotPromise<UserOperationStruct>): string {
return packUserOp(userOp, false)
encodeUserOP (userOp: UserOperation): string {
return encodeUserOp(userOp, false)
}
async encodeUserOpCallDataAndGasLimit (detailsForUserOp: TransactionDetailsForUserOp): Promise<{ callData: string, callGasLimit: BigNumber }> {
@@ -193,10 +191,9 @@ export abstract class BaseAccountAPI {
/**
* return userOpHash for signing.
* This value matches entryPoint.getUserOpHash (calculated off-chain, to avoid a view call)
* @param userOp userOperation, (signature field ignored)
* @param op userOperation, (signature field ignored)
*/
async getUserOpHash (userOp: UserOperationStruct): Promise<string> {
const op = await resolveProperties(userOp)
async getUserOpHash (op: UserOperation): Promise<string> {
const chainId = await this.provider.getNetwork().then(net => net.chainId)
return getUserOpHash(op, this.entryPointAddress, chainId)
}
@@ -216,11 +213,11 @@ export abstract class BaseAccountAPI {
return this.senderAddress
}
async estimateCreationGas (initCode?: string): Promise<BigNumberish> {
if (initCode == null || initCode === '0x') return 0
const deployerAddress = initCode.substring(0, 42)
const deployerCallData = '0x' + initCode.substring(42)
return await this.provider.estimateGas({ to: deployerAddress, data: deployerCallData })
async estimateCreationGas (factoryParams: FactoryParams | null): Promise<BigNumberish> {
if (factoryParams == null) {
return 0
}
return await this.provider.estimateGas({ to: factoryParams.factory, data: factoryParams.factoryData })
}
/**
@@ -229,14 +226,14 @@ export abstract class BaseAccountAPI {
* - if gas or nonce are missing, read them from the chain (note that we can't fill gaslimit before the account is created)
* @param info
*/
async createUnsignedUserOp (info: TransactionDetailsForUserOp): Promise<UserOperationStruct> {
async createUnsignedUserOp (info: TransactionDetailsForUserOp): Promise<UserOperation> {
const {
callData,
callGasLimit
} = await this.encodeUserOpCallDataAndGasLimit(info)
const initCode = await this.getInitCode()
const factoryParams = await this.getRequiredFactoryData()
const initGas = await this.estimateCreationGas(initCode)
const initGas = await this.estimateCreationGas(factoryParams)
const verificationGasLimit = BigNumber.from(await this.getVerificationGasLimit())
.add(initGas)
@@ -254,31 +251,34 @@ export abstract class BaseAccountAPI {
}
}
const partialUserOp: any = {
sender: this.getAccountAddress(),
nonce: info.nonce ?? this.getNonce(),
initCode,
let partialUserOp = {
sender: await this.getAccountAddress(),
nonce: info.nonce ?? await this.getNonce(),
factory: factoryParams?.factory,
factoryData: factoryParams?.factoryData,
callData,
callGasLimit,
verificationGasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
paymasterAndData: '0x'
maxFeePerGas: maxFeePerGas as any,
maxPriorityFeePerGas: maxPriorityFeePerGas as any
}
let paymasterAndData: string | undefined
if (this.paymasterAPI != null) {
// fill (partial) preVerificationGas (all except the cost of the generated paymasterAndData)
const userOpForPm = {
...partialUserOp,
preVerificationGas: await this.getPreVerificationGas(partialUserOp)
const pmFields = await this.paymasterAPI.getTemporaryPaymasterData(partialUserOp)
if (pmFields != null) {
partialUserOp = {
...partialUserOp,
paymaster: pmFields?.paymaster,
paymasterPostOpGasLimit: pmFields?.paymasterPostOpGasLimit,
paymasterVerificationGasLimit: pmFields?.paymasterVerificationGasLimit,
paymasterData: pmFields?.paymasterData
} as any
}
paymasterAndData = await this.paymasterAPI.getPaymasterAndData(userOpForPm)
}
partialUserOp.paymasterAndData = paymasterAndData ?? '0x'
return {
...partialUserOp,
preVerificationGas: this.getPreVerificationGas(partialUserOp),
preVerificationGas: await this.getPreVerificationGas(partialUserOp),
signature: ''
}
}
@@ -287,9 +287,9 @@ export abstract class BaseAccountAPI {
* Sign the filled userOp.
* @param userOp the UserOperation to sign (with signature field ignored)
*/
async signUserOp (userOp: UserOperationStruct): Promise<UserOperationStruct> {
async signUserOp (userOp: UserOperation): Promise<UserOperation> {
const userOpHash = await this.getUserOpHash(userOp)
const signature = this.signUserOpHash(userOpHash)
const signature = await this.signUserOpHash(userOpHash)
return {
...userOp,
signature
@@ -300,7 +300,7 @@ export abstract class BaseAccountAPI {
* helper method: create and sign a user operation.
* @param info transaction details for the userOp
*/
async createSignedUserOp (info: TransactionDetailsForUserOp): Promise<UserOperationStruct> {
async createSignedUserOp (info: TransactionDetailsForUserOp): Promise<UserOperation> {
return await this.signUserOp(await this.createUnsignedUserOp(info))
}

View File

@@ -1,14 +1,13 @@
import { BaseProvider, TransactionReceipt, TransactionResponse } from '@ethersproject/providers'
import { BigNumber, Signer } from 'ethers'
import { Network } from '@ethersproject/networks'
import { hexValue, resolveProperties } from 'ethers/lib/utils'
import { hexValue } from 'ethers/lib/utils'
import { ClientConfig } from './ClientConfig'
import { ERC4337EthersSigner } from './ERC4337EthersSigner'
import { UserOperationEventListener } from './UserOperationEventListener'
import { HttpRpcClient } from './HttpRpcClient'
import { EntryPoint, UserOperationStruct } from '@account-abstraction/contracts'
import { getUserOpHash } from '@account-abstraction/utils'
import { getUserOpHash, IEntryPoint, UserOperation } from '@account-abstraction/utils'
import { BaseAccountAPI } from './BaseAccountAPI'
import Debug from 'debug'
const debug = Debug('aa.provider')
@@ -24,7 +23,7 @@ export class ERC4337EthersProvider extends BaseProvider {
readonly originalSigner: Signer,
readonly originalProvider: BaseProvider,
readonly httpRpcClient: HttpRpcClient,
readonly entryPoint: EntryPoint,
readonly entryPoint: IEntryPoint,
readonly smartAccountAPI: BaseAccountAPI
) {
super({
@@ -89,8 +88,7 @@ export class ERC4337EthersProvider extends BaseProvider {
}
// fabricate a response in a format usable by ethers users...
async constructUserOpTransactionResponse (userOp1: UserOperationStruct): Promise<TransactionResponse> {
const userOp = await resolveProperties(userOp1)
async constructUserOpTransactionResponse (userOp: UserOperation): Promise<TransactionResponse> {
const userOpHash = getUserOpHash(userOp, this.config.entryPointAddress, this.chainId)
const waitForUserOp = async (): Promise<TransactionReceipt> => await new Promise((resolve, reject) => {
new UserOperationEventListener(
@@ -102,13 +100,13 @@ export class ERC4337EthersProvider extends BaseProvider {
confirmations: 0,
from: userOp.sender,
nonce: BigNumber.from(userOp.nonce).toNumber(),
gasLimit: BigNumber.from(userOp.callGasLimit), // ??
gasLimit: BigNumber.from(userOp.callGasLimit),
value: BigNumber.from(0),
data: hexValue(userOp.callData), // should extract the actual called method from this "execFromEntryPoint()" call
chainId: this.chainId,
wait: async (confirmations?: number): Promise<TransactionReceipt> => {
const transactionReceipt = await waitForUserOp()
if (userOp.initCode.length !== 0) {
if (userOp.factory != null) {
// checking if the wallet has been deployed by the transaction; it must be if we are here
await this.smartAccountAPI.checkAccountPhantom()
}

View File

@@ -6,8 +6,8 @@ import { Bytes } from 'ethers'
import { ERC4337EthersProvider } from './ERC4337EthersProvider'
import { ClientConfig } from './ClientConfig'
import { HttpRpcClient } from './HttpRpcClient'
import { UserOperationStruct } from '@account-abstraction/contracts'
import { BaseAccountAPI } from './BaseAccountAPI'
import { UserOperation } from '@account-abstraction/utils'
export class ERC4337EthersSigner extends Signer {
// TODO: we have 'erc4337provider', remove shared dependencies or avoid two-way reference
@@ -94,7 +94,7 @@ export class ERC4337EthersSigner extends Signer {
throw new Error('not implemented')
}
async signUserOperation (userOperation: UserOperationStruct): Promise<string> {
async signUserOperation (userOperation: UserOperation): Promise<string> {
const message = await this.smartAccountAPI.getUserOpHash(userOperation)
return await this.originalSigner.signMessage(message)
}

View File

@@ -1,9 +1,8 @@
import { JsonRpcProvider } from '@ethersproject/providers'
import { ethers } from 'ethers'
import { resolveProperties } from 'ethers/lib/utils'
import { UserOperationStruct } from '@account-abstraction/contracts'
import Debug from 'debug'
import { deepHexlify } from '@account-abstraction/utils'
import { deepHexlify, UserOperation } from '@account-abstraction/utils'
const debug = Debug('aa.rpc')
@@ -38,10 +37,10 @@ export class HttpRpcClient {
* @param userOp1
* @return userOpHash the id of this operation, for getUserOperationTransaction
*/
async sendUserOpToBundler (userOp1: UserOperationStruct): Promise<string> {
async sendUserOpToBundler (userOp1: UserOperation): Promise<string> {
await this.initializing
const hexifiedUserOp = deepHexlify(await resolveProperties(userOp1))
const jsonRequestData: [UserOperationStruct, string] = [hexifiedUserOp, this.entryPointAddress]
const jsonRequestData: [UserOperation, string] = [hexifiedUserOp, this.entryPointAddress]
await this.printUserOperation('eth_sendUserOperation', jsonRequestData)
return await this.userOpJsonRpcProvider
.send('eth_sendUserOperation', [hexifiedUserOp, this.entryPointAddress])
@@ -52,17 +51,16 @@ export class HttpRpcClient {
* @param userOp1
* @returns latest gas suggestions made by the bundler.
*/
async estimateUserOpGas (userOp1: Partial<UserOperationStruct>): Promise<{callGasLimit: number, preVerificationGas: number, verificationGasLimit: number}> {
async estimateUserOpGas (userOp1: Partial<UserOperation>): Promise<{callGasLimit: number, preVerificationGas: number, verificationGasLimit: number}> {
await this.initializing
const hexifiedUserOp = deepHexlify(await resolveProperties(userOp1))
const jsonRequestData: [UserOperationStruct, string] = [hexifiedUserOp, this.entryPointAddress]
const hexifiedUserOp = deepHexlify(userOp1)
const jsonRequestData: [UserOperation, string] = [hexifiedUserOp, this.entryPointAddress]
await this.printUserOperation('eth_estimateUserOperationGas', jsonRequestData)
return await this.userOpJsonRpcProvider
.send('eth_estimateUserOperationGas', [hexifiedUserOp, this.entryPointAddress])
}
private async printUserOperation (method: string, [userOp1, entryPointAddress]: [UserOperationStruct, string]): Promise<void> {
const userOp = await resolveProperties(userOp1)
private async printUserOperation (method: string, [userOp, entryPointAddress]: [UserOperation, string]): Promise<void> {
debug('sending', method, {
...userOp
// initCode: (userOp.initCode ?? '').length,

View File

@@ -1,16 +1,40 @@
import { UserOperationStruct } from '@account-abstraction/contracts'
import { UserOperation } from '@account-abstraction/utils'
import { BigNumberish, BytesLike } from 'ethers'
/**
* returned paymaster parameters.
* note that if a paymaster is specified, then the gasLimits must be specified
* (even if postOp is not called, the paymasterPostOpGasLimit must be set to zero)
*/
export interface PaymasterParams {
paymaster: string
paymasterData?: BytesLike
paymasterVerificationGasLimit: BigNumberish
paymasterPostOpGasLimit: BigNumberish
}
/**
* an API to external a UserOperation with paymaster info
*/
export class PaymasterAPI {
/**
* return temporary values to put into the paymaster fields.
* @param userOp the partially-filled UserOperation. Should be filled with tepmorary values for all
* fields except paymaster fields.
* @return temporary paymaster parameters, that can be used for gas estimations
*/
async getTemporaryPaymasterData (userOp: Partial<UserOperation>): Promise<PaymasterParams | null> {
return null
}
/**
* after gas estimation, return final paymaster parameters to replace the above tepmorary value.
* @param userOp a partially-filled UserOperation (without signature and paymasterAndData
* note that the "preVerificationGas" is incomplete: it can't account for the
* paymasterAndData value, which will only be returned by this method..
* @returns the value to put into the PaymasterAndData, undefined to leave it empty
* @returns the values to put into paymaster fields, null to leave them empty
*/
async getPaymasterAndData (userOp: Partial<UserOperationStruct>): Promise<string | undefined> {
return '0x'
async getPaymasterData (userOp: Partial<UserOperation>): Promise<PaymasterParams | null> {
return null
}
}

View File

@@ -1,13 +1,11 @@
import { JsonRpcProvider } from '@ethersproject/providers'
import { EntryPoint__factory, SimpleAccountFactory__factory } from '@account-abstraction/contracts'
import { ClientConfig } from './ClientConfig'
import { SimpleAccountAPI } from './SimpleAccountAPI'
import { ERC4337EthersProvider } from './ERC4337EthersProvider'
import { HttpRpcClient } from './HttpRpcClient'
import { DeterministicDeployer } from './DeterministicDeployer'
import { Signer } from '@ethersproject/abstract-signer'
import { DeterministicDeployer, IEntryPoint__factory, SimpleAccountFactory__factory } from '@account-abstraction/utils'
/**
* wrap an existing provider to tunnel requests through Account Abstraction.
@@ -20,7 +18,7 @@ export async function wrapProvider (
config: ClientConfig,
originalSigner: Signer = originalProvider.getSigner()
): Promise<ERC4337EthersProvider> {
const entryPoint = EntryPoint__factory.connect(config.entryPointAddress, originalProvider)
const entryPoint = IEntryPoint__factory.connect(config.entryPointAddress, originalProvider)
// Initial SimpleAccount instance is not deployed and exists just for the interface
const detDeployer = new DeterministicDeployer(originalProvider)
const SimpleAccountFactory = await detDeployer.deterministicDeploy(new SimpleAccountFactory__factory(), 0, [entryPoint.address])

View File

@@ -3,11 +3,11 @@ import {
SimpleAccount,
SimpleAccount__factory, SimpleAccountFactory,
SimpleAccountFactory__factory
} from '@account-abstraction/contracts'
} from '@account-abstraction/utils'
import { arrayify, hexConcat } from 'ethers/lib/utils'
import { arrayify } from 'ethers/lib/utils'
import { Signer } from '@ethersproject/abstract-signer'
import { BaseApiParams, BaseAccountAPI } from './BaseAccountAPI'
import { BaseApiParams, BaseAccountAPI, FactoryParams } from './BaseAccountAPI'
/**
* constructor params, added no top of base params:
@@ -60,7 +60,7 @@ export class SimpleAccountAPI extends BaseAccountAPI {
* return the value to put into the "initCode" field, if the account is not yet deployed.
* this value holds the "factory" address, followed by this account's information
*/
async getAccountInitCode (): Promise<string> {
async getFactoryData (): Promise<FactoryParams | null> {
if (this.factory == null) {
if (this.factoryAddress != null && this.factoryAddress !== '') {
this.factory = SimpleAccountFactory__factory.connect(this.factoryAddress, this.provider)
@@ -68,10 +68,10 @@ export class SimpleAccountAPI extends BaseAccountAPI {
throw new Error('no factory to get initCode')
}
}
return hexConcat([
this.factory.address,
this.factory.interface.encodeFunctionData('createAccount', [await this.owner.getAddress(), this.index])
])
return {
factory: this.factory.address,
factoryData: this.factory.interface.encodeFunctionData('createAccount', [await this.owner.getAddress(), this.index])
}
}
async getNonce (): Promise<BigNumber> {

View File

@@ -1,8 +1,8 @@
import { BigNumberish, Event } from 'ethers'
import { TransactionReceipt } from '@ethersproject/providers'
import { EntryPoint } from '@account-abstraction/contracts'
import { defaultAbiCoder } from 'ethers/lib/utils'
import Debug from 'debug'
import { IEntryPoint } from '@account-abstraction/utils'
const debug = Debug('aa.listener')
@@ -19,7 +19,7 @@ export class UserOperationEventListener {
constructor (
readonly resolve: (t: TransactionReceipt) => void,
readonly reject: (reason?: any) => void,
readonly entryPoint: EntryPoint,
readonly entryPoint: IEntryPoint,
readonly sender: string,
readonly userOpHash: string,
readonly nonce?: BigNumberish,

View File

@@ -1,5 +1,4 @@
import { UserOperationStruct } from '@account-abstraction/contracts'
import { NotPromise, packUserOp } from '@account-abstraction/utils'
import { encodeUserOp, packUserOp, UserOperation } from '@account-abstraction/utils'
import { arrayify, hexlify } from 'ethers/lib/utils'
export interface GasOverheads {
@@ -58,16 +57,16 @@ export const DefaultGasOverheads: GasOverheads = {
* @param userOp filled userOp to calculate. The only possible missing fields can be the signature and preVerificationGas itself
* @param overheads gas overheads to use, to override the default values
*/
export function calcPreVerificationGas (userOp: Partial<NotPromise<UserOperationStruct>>, overheads?: Partial<GasOverheads>): number {
export function calcPreVerificationGas (userOp: Partial<UserOperation>, overheads?: Partial<GasOverheads>): number {
const ov = { ...DefaultGasOverheads, ...(overheads ?? {}) }
const p: NotPromise<UserOperationStruct> = {
const p: UserOperation = {
// dummy values, in case the UserOp is incomplete.
preVerificationGas: 21000, // dummy value, just for calldata cost
signature: hexlify(Buffer.alloc(ov.sigSize, 1)), // dummy signature
...userOp
} as any
const packed = arrayify(packUserOp(p, false))
const packed = arrayify(encodeUserOp(packUserOp(p), false))
const lengthInWord = (packed.length + 31) / 32
const callDataCost = packed.map(x => x === 0 ? ov.zeroByte : ov.nonZeroByte).reduce((sum, x) => sum + x)
const ret = Math.round(

View File

@@ -6,5 +6,4 @@ export { ERC4337EthersSigner } from './ERC4337EthersSigner'
export { ERC4337EthersProvider } from './ERC4337EthersProvider'
export { ClientConfig } from './ClientConfig'
export { HttpRpcClient } from './HttpRpcClient'
export { DeterministicDeployer } from './DeterministicDeployer'
export * from './calcPreVerificationGas'

View File

@@ -1,8 +1,7 @@
import { expect } from 'chai'
import { SampleRecipient__factory } from '@account-abstraction/utils'
import { DeterministicDeployer, SampleRecipient__factory } from '@account-abstraction/utils'
import { ethers } from 'hardhat'
import { hexValue } from 'ethers/lib/utils'
import { DeterministicDeployer } from '../src/DeterministicDeployer'
const deployer = new DeterministicDeployer(ethers.provider)
@@ -18,7 +17,7 @@ describe('#deterministicDeployer', () => {
it('should deploy at given address', async () => {
const ctr = hexValue(new SampleRecipient__factory(ethers.provider.getSigner()).getDeployTransaction().data!)
DeterministicDeployer.init(ethers.provider)
const addr = await DeterministicDeployer.getAddress(ctr)
const addr = DeterministicDeployer.getAddress(ctr)
expect(await deployer.isContractDeployed(addr)).to.equal(false)
await DeterministicDeployer.deploy(ctr)
expect(await deployer.isContractDeployed(addr)).to.equal(true)

View File

@@ -0,0 +1,117 @@
import { expect } from 'chai'
import {
packAccountGasLimits,
packPaymasterData, packUint,
packUserOp,
unpackAccountGasLimits,
unpackPaymasterAndData
} from '@account-abstraction/utils'
import { hexConcat, hexlify, hexZeroPad } from 'ethers/lib/utils'
import { BigNumber } from 'ethers'
describe('utils', () => {
describe('userop pack/unpack functions', () => {
const paymaster = '0xaa'.padEnd(42, 'a')
it('#packAccountGasLimits', function () {
expect(packAccountGasLimits(0xaa, 0xbbbb)).to.eql(
hexConcat([hexZeroPad('0xaa', 16), hexZeroPad('0xbbbb', 16)])
)
})
it('#unpackAccountGasLimits', function () {
const packed = hexConcat([hexZeroPad('0xaa', 16), hexZeroPad('0xbbbb', 16)])
expect(unpackAccountGasLimits(packed))
.to.eql({ verificationGasLimit: BigNumber.from(0xaa), callGasLimit: BigNumber.from(0xbbbb) })
})
it('#packPaymasterAndData', () => {
const pmVerificationGas = 1
const postOpGas = 2
expect(packPaymasterData(paymaster, pmVerificationGas, postOpGas))
.to.eql(hexConcat([
paymaster,
hexZeroPad(hexlify(pmVerificationGas), 16),
hexZeroPad(hexlify(postOpGas), 16)
]))
const pmData = '0xdeadface'
expect(packPaymasterData(paymaster, pmVerificationGas, postOpGas, pmData))
.to.eql(hexConcat([
paymaster,
hexZeroPad(hexlify(pmVerificationGas), 16),
hexZeroPad(hexlify(postOpGas), 16),
pmData
]))
})
it('#packPaymasterAndData', () => {
const paymasterVerificationGas = BigNumber.from(1)
const postOpGasLimit = BigNumber.from(2)
expect(unpackPaymasterAndData(packPaymasterData(paymaster, paymasterVerificationGas, postOpGasLimit)))
.to.eql({ paymaster, paymasterVerificationGas, postOpGasLimit, paymasterData: '0x' })
const paymasterData = '0xbeaf'
expect(unpackPaymasterAndData(packPaymasterData(paymaster, paymasterVerificationGas, postOpGasLimit, paymasterData)))
.to.eql({ paymaster, paymasterVerificationGas, postOpGasLimit, paymasterData })
})
it('should pack userop without optional fields', function () {
expect(packUserOp({
sender: 'a',
nonce: 1,
callGasLimit: 2,
verificationGasLimit: 3,
preVerificationGas: 4,
callData: '333',
maxFeePerGas: 5,
maxPriorityFeePerGas: 6,
signature: '777'
})).to.eql({
sender: 'a',
nonce: '0x01',
initCode: '0x',
accountGasLimits: packAccountGasLimits(3, 2),
preVerificationGas: '0x04',
callData: '333',
gasFees: packUint(6, 5),
signature: '777',
paymasterAndData: '0x'
})
})
it('should pack userop with optional fields', function () {
const factory = '0xfa'.padEnd(42, 'fa')
expect(packUserOp({
sender: 'a',
nonce: 1,
factory,
factoryData: '0xbeaf',
callGasLimit: 2,
verificationGasLimit: 3,
preVerificationGas: 4,
callData: '333',
maxFeePerGas: 5,
maxPriorityFeePerGas: 6,
signature: '777',
paymaster,
paymasterVerificationGasLimit: 8,
paymasterPostOpGasLimit: 9,
paymasterData: '0xcafebabe'
})).to.eql({
sender: 'a',
nonce: '0x01',
initCode: hexConcat([factory, '0xbeaf']),
accountGasLimits: packAccountGasLimits(3, 2),
preVerificationGas: '0x04',
gasFees: packUint(6, 5),
callData: '333',
signature: '777',
paymasterAndData: hexConcat([
paymaster,
hexZeroPad('0x8', 16),
hexZeroPad('0x9', 16),
'0xcafebabe'
])
})
})
})
})

View File

@@ -1,16 +1,19 @@
import {
EntryPoint,
EntryPoint__factory,
SimpleAccountFactory__factory,
UserOperationStruct
} from '@account-abstraction/contracts'
import { Wallet } from 'ethers'
import { parseEther } from 'ethers/lib/utils'
import { expect } from 'chai'
import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'
import { ethers } from 'hardhat'
import { DeterministicDeployer, SimpleAccountAPI } from '../src'
import { SampleRecipient, SampleRecipient__factory, rethrowError } from '@account-abstraction/utils'
import { SimpleAccountAPI } from '../src'
import {
deployEntryPoint,
DeterministicDeployer,
UserOperation,
packUserOp,
SampleRecipient,
SampleRecipient__factory,
IEntryPoint,
SimpleAccountFactory__factory, decodeRevertReason
} from '@account-abstraction/utils'
const provider = ethers.provider
const signer = provider.getSigner()
@@ -18,14 +21,14 @@ const signer = provider.getSigner()
describe('SimpleAccountAPI', () => {
let owner: Wallet
let api: SimpleAccountAPI
let entryPoint: EntryPoint
let entryPoint: IEntryPoint
let beneficiary: string
let recipient: SampleRecipient
let accountAddress: string
let accountDeployed = false
before('init', async () => {
entryPoint = await new EntryPoint__factory(signer).deploy()
entryPoint = await deployEntryPoint(provider)
beneficiary = await signer.getAddress()
recipient = await new SampleRecipient__factory(signer).deploy()
@@ -41,21 +44,19 @@ describe('SimpleAccountAPI', () => {
})
it('#getUserOpHash should match entryPoint.getUserOpHash', async function () {
const userOp: UserOperationStruct = {
const userOp: UserOperation = {
sender: '0x'.padEnd(42, '1'),
nonce: 2,
initCode: '0x3333',
callData: '0x4444',
callGasLimit: 5,
verificationGasLimit: 6,
preVerificationGas: 7,
maxFeePerGas: 8,
maxPriorityFeePerGas: 9,
paymasterAndData: '0xaaaaaa',
signature: '0xbbbb'
}
const hash = await api.getUserOpHash(userOp)
const epHash = await entryPoint.getUserOpHash(userOp)
const epHash = await entryPoint.getUserOpHash(packUserOp(userOp))
expect(hash).to.equal(epHash)
})
@@ -72,14 +73,14 @@ describe('SimpleAccountAPI', () => {
data: recipient.interface.encodeFunctionData('something', ['hello'])
})
await expect(entryPoint.handleOps([op], beneficiary)).to.emit(recipient, 'Sender')
await expect(entryPoint.handleOps([packUserOp(op)], beneficiary)).to.emit(recipient, 'Sender')
.withArgs(anyValue, accountAddress, 'hello')
expect(await provider.getCode(accountAddress).then(code => code.length)).to.greaterThan(1000)
expect(await provider.getCode(accountAddress).then(code => code.length)).to.greaterThan(100)
accountDeployed = true
})
context('#rethrowError', () => {
let userOp: UserOperationStruct
let userOp: UserOperation
before(async () => {
userOp = await api.createUnsignedUserOp({
target: ethers.constants.AddressZero,
@@ -89,10 +90,8 @@ describe('SimpleAccountAPI', () => {
userOp.signature = '0x11'
})
it('should parse FailedOp error', async () => {
await expect(
entryPoint.handleOps([userOp], beneficiary)
.catch(rethrowError))
.to.revertedWith('FailedOp: AA23 reverted: ECDSA: invalid signature length')
expect(await entryPoint.handleOps([packUserOp(userOp)], beneficiary).catch(decodeRevertReason))
.to.eql('FailedOpWithRevert(0,"AA23 reverted",ECDSAInvalidSignatureLength(1))')
})
it('should parse Error(message) error', async () => {
await expect(
@@ -122,7 +121,7 @@ describe('SimpleAccountAPI', () => {
target: recipient.address,
data: recipient.interface.encodeFunctionData('something', ['world'])
})
await expect(entryPoint.handleOps([op1], beneficiary)).to.emit(recipient, 'Sender')
await expect(entryPoint.handleOps([packUserOp(op1)], beneficiary)).to.emit(recipient, 'Sender')
.withArgs(anyValue, accountAddress, 'world')
})
})

View File

@@ -1,7 +1,12 @@
import { SampleRecipient, SampleRecipient__factory } from '@account-abstraction/utils'
import {
deployEntryPoint,
IEntryPoint,
packUserOp,
SampleRecipient,
SampleRecipient__factory
} from '@account-abstraction/utils'
import { ethers } from 'hardhat'
import { ClientConfig, ERC4337EthersProvider, wrapProvider } from '../src'
import { EntryPoint, EntryPoint__factory } from '@account-abstraction/contracts'
import { expect } from 'chai'
import { parseEther } from 'ethers/lib/utils'
import { Wallet } from 'ethers'
@@ -13,10 +18,10 @@ const signer = provider.getSigner()
describe('ERC4337EthersSigner, Provider', function () {
let recipient: SampleRecipient
let aaProvider: ERC4337EthersProvider
let entryPoint: EntryPoint
let entryPoint: IEntryPoint
before('init', async () => {
const deployRecipient = await new SampleRecipient__factory(signer).deploy()
entryPoint = await new EntryPoint__factory(signer).deploy()
entryPoint = await deployEntryPoint(provider)
const config: ClientConfig = {
entryPointAddress: entryPoint.address,
bundlerUrl: ''
@@ -28,10 +33,10 @@ describe('ERC4337EthersSigner, Provider', function () {
// for testing: bypass sending through a bundler, and send directly to our entrypoint..
aaProvider.httpRpcClient.sendUserOpToBundler = async (userOp) => {
try {
await entryPoint.handleOps([userOp], beneficiary)
await entryPoint.handleOps([packUserOp(userOp)], beneficiary)
} catch (e: any) {
// doesn't report error unless called with callStatic
await entryPoint.callStatic.handleOps([userOp], beneficiary).catch((e: any) => {
await entryPoint.callStatic.handleOps([packUserOp(userOp)], beneficiary).catch((e: any) => {
// eslint-disable-next-line
const message = e.errorArgs != null ? `${e.errorName}(${e.errorArgs.join(',')})` : e.message
throw new Error(message)

View File

@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;
import "@account-abstraction/contracts/core/EntryPointSimulations.sol";
import "@account-abstraction/contracts/interfaces/IStakeManager.sol";
import "@account-abstraction/contracts/samples/SimpleAccountFactory.sol";
import "@account-abstraction/contracts/samples/TokenPaymaster.sol";

View File

@@ -9,7 +9,7 @@ const config: HardhatUserConfig = {
target: 'ethers-v5'
},
solidity: {
version: '0.8.15',
version: '0.8.23',
settings: {
optimizer: { enabled: true }
}

View File

@@ -1,6 +1,6 @@
{
"name": "@account-abstraction/utils",
"version": "0.6.0",
"version": "0.7.0",
"main": "./dist/src/index.js",
"license": "MIT",
"files": [
@@ -13,19 +13,23 @@
"hardhat-compile": "hardhat compile",
"lint-fix": "eslint -f unix . --fix",
"watch-tsc": "tsc -w --preserveWatchOutput",
"tsc": "tsc"
"tsc": "tsc",
"sol-ts": "yarn hardhat-compile && yarn tsc"
},
"dependencies": {
"@account-abstraction/contracts": "^0.6.0",
"@account-abstraction/contracts": "^0.7.0",
"@ethersproject/abi": "^5.7.0",
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"@openzeppelin/contracts": "^4.7.3",
"@openzeppelin/contracts": "^5.0.1",
"debug": "^4.3.4",
"ethereumjs-util": "^7.1.5",
"ethers": "^5.7.0"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^1.0.2",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"hardhat": "^2.11.0"
"hardhat": "^2.17.0"
}
}

View File

@@ -1,5 +1,6 @@
import { BigNumber, BigNumberish, ContractFactory } from 'ethers'
import { hexConcat, hexlify, hexZeroPad, keccak256 } from 'ethers/lib/utils'
import { toChecksumAddress } from 'ethereumjs-util'
import { TransactionRequest } from '@ethersproject/abstract-provider'
import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'
import { Signer } from '@ethersproject/abstract-signer'
@@ -104,12 +105,12 @@ export class DeterministicDeployer {
const saltEncoded = hexZeroPad(hexlify(salt), 32)
const ctrCode1 = DeterministicDeployer.getCtrCode(ctrCode, params)
return '0x' + keccak256(hexConcat([
return toChecksumAddress('0x' + keccak256(hexConcat([
'0xff',
DeterministicDeployer.proxyAddress,
saltEncoded,
keccak256(ctrCode1)
])).slice(-40)
])).slice(-40))
}
async deterministicDeploy (ctrCode: string | ContractFactory, salt: BigNumberish = 0, params: any[] = []): Promise<string> {

View File

@@ -1,16 +1,25 @@
import { defaultAbiCoder, hexConcat, hexlify, keccak256, resolveProperties } from 'ethers/lib/utils'
import { UserOperationStruct } from '@account-abstraction/contracts'
import {
defaultAbiCoder,
hexConcat, hexDataLength,
hexDataSlice,
hexlify,
hexZeroPad,
keccak256,
resolveProperties
} from 'ethers/lib/utils'
import { abi as entryPointAbi } from '@account-abstraction/contracts/artifacts/IEntryPoint.json'
import { ethers } from 'ethers'
import { BigNumber, BigNumberish, BytesLike, ethers } from 'ethers'
import Debug from 'debug'
import { PackedUserOperation } from './Utils'
const debug = Debug('aa.utils')
// UserOperation is the first parameter of validateUseOp
const validateUserOpMethod = 'simulateValidation'
const UserOpType = entryPointAbi.find(entry => entry.name === validateUserOpMethod)?.inputs[0]
if (UserOpType == null) {
throw new Error(`unable to find method ${validateUserOpMethod} in EP ${entryPointAbi.filter(x => x.type === 'function').map(x => x.name).join(',')}`)
// UserOperation is the first parameter of getUserOpHash
const getUserOpHashMethod = 'getUserOpHash'
const PackedUserOpType = entryPointAbi.find(entry => entry.name === getUserOpHashMethod)?.inputs[0]
if (PackedUserOpType == null) {
throw new Error(`unable to find method ${getUserOpHashMethod} in EP ${entryPointAbi.filter(x => x.type === 'function').map(x => x.name).join(',')}`)
}
export const AddressZero = ethers.constants.AddressZero
@@ -20,29 +29,220 @@ export type NotPromise<T> = {
[P in keyof T]: Exclude<T[P], Promise<any>>
}
export interface UserOperation {
sender: string
nonce: BigNumberish
factory?: string
factoryData?: BytesLike
callData: BytesLike
callGasLimit: BigNumberish
verificationGasLimit: BigNumberish
preVerificationGas: BigNumberish
maxFeePerGas: BigNumberish
maxPriorityFeePerGas: BigNumberish
paymaster?: string
paymasterVerificationGasLimit?: BigNumberish
paymasterPostOpGasLimit?: BigNumberish
paymasterData?: BytesLike
signature: BytesLike
}
// todo: remove this wrapper method?
export function packAccountGasLimits (validationGasLimit: BigNumberish, callGasLimit: BigNumberish): string {
return packUint(validationGasLimit, callGasLimit)
}
export function unpackAccountGasLimits (accountGasLimits: BytesLike): {
verificationGasLimit: BigNumber
callGasLimit: BigNumber
} {
const [verificationGasLimit, callGasLimit] = unpackUint(accountGasLimits)
return { verificationGasLimit, callGasLimit }
}
export function packUint (high128: BigNumberish, low128: BigNumberish): string {
return hexZeroPad(BigNumber.from(high128).shl(128).add(low128).toHexString(), 32)
}
export function unpackUint (packed: BytesLike): [high128: BigNumber, low128: BigNumber] {
const packedNumber: BigNumber = BigNumber.from(packed)
return [packedNumber.shr(128), packedNumber.and(BigNumber.from(1).shl(128).sub(1))]
}
export function packPaymasterData (paymaster: string, paymasterVerificationGasLimit: BigNumberish, postOpGasLimit: BigNumberish, paymasterData?: BytesLike): BytesLike {
return ethers.utils.hexConcat([
paymaster,
packUint(paymasterVerificationGasLimit, postOpGasLimit),
paymasterData ?? '0x'
])
}
export interface ValidationData {
aggregator: string
validAfter: number
validUntil: number
}
export const maxUint48 = (2 ** 48) - 1
export const SIG_VALIDATION_FAILED = hexZeroPad('0x01', 20)
/**
* pack the userOperation
* @param op
* parse validationData as returned from validateUserOp or validatePaymasterUserOp into ValidationData struct
* @param validationData
*/
export function parseValidationData (validationData: BigNumberish): ValidationData {
const data = hexZeroPad(BigNumber.from(validationData).toHexString(), 32)
// string offsets start from left (msb)
const aggregator = hexDataSlice(data, 32 - 20)
let validUntil = parseInt(hexDataSlice(data, 32 - 26, 32 - 20))
if (validUntil === 0) validUntil = maxUint48
const validAfter = parseInt(hexDataSlice(data, 0, 6))
return {
aggregator,
validAfter,
validUntil
}
}
export function mergeValidationDataValues (accountValidationData: BigNumberish, paymasterValidationData: BigNumberish): ValidationData {
return mergeValidationData(
parseValidationData(accountValidationData),
parseValidationData(paymasterValidationData)
)
}
/**
* merge validationData structure returned by paymaster and account
* @param accountValidationData returned from validateUserOp
* @param paymasterValidationData returned from validatePaymasterUserOp
*/
export function mergeValidationData (accountValidationData: ValidationData, paymasterValidationData: ValidationData): ValidationData {
return {
aggregator: paymasterValidationData.aggregator !== AddressZero ? SIG_VALIDATION_FAILED : accountValidationData.aggregator,
validAfter: Math.max(accountValidationData.validAfter, paymasterValidationData.validAfter),
validUntil: Math.min(accountValidationData.validUntil, paymasterValidationData.validUntil)
}
}
export function packValidationData (validationData: ValidationData): BigNumber {
return BigNumber.from(validationData.validAfter ?? 0).shl(48)
.add(validationData.validUntil ?? 0).shl(160)
.add(validationData.aggregator)
}
export function unpackPaymasterAndData (paymasterAndData: BytesLike): {
paymaster: string
paymasterVerificationGas: BigNumber
postOpGasLimit: BigNumber
paymasterData: BytesLike
} | null {
if (paymasterAndData.length <= 2) return null
if (hexDataLength(paymasterAndData) < 52) {
// if length is non-zero, then must at least host paymaster address and gas-limits
throw new Error(`invalid PaymasterAndData: ${paymasterAndData as string}`)
}
const [paymasterVerificationGas, postOpGasLimit] = unpackUint(hexDataSlice(paymasterAndData, 20, 52))
return {
paymaster: hexDataSlice(paymasterAndData, 0, 20),
paymasterVerificationGas,
postOpGasLimit,
paymasterData: hexDataSlice(paymasterAndData, 52)
}
}
export function packUserOp (op: UserOperation): PackedUserOperation {
let paymasterAndData: BytesLike
if (op.paymaster == null) {
paymasterAndData = '0x'
} else {
if (op.paymasterVerificationGasLimit == null || op.paymasterPostOpGasLimit == null) {
throw new Error('paymaster with no gas limits')
}
paymasterAndData = packPaymasterData(op.paymaster, op.paymasterVerificationGasLimit, op.paymasterPostOpGasLimit, op.paymasterData)
}
return {
sender: op.sender,
nonce: BigNumber.from(op.nonce).toHexString(),
initCode: op.factory == null ? '0x' : hexConcat([op.factory, op.factoryData ?? '']),
callData: op.callData,
accountGasLimits: packUint(op.verificationGasLimit, op.callGasLimit),
preVerificationGas: BigNumber.from(op.preVerificationGas).toHexString(),
gasFees: packUint(op.maxPriorityFeePerGas, op.maxFeePerGas),
paymasterAndData,
signature: op.signature
}
}
export function unpackUserOp (packed: PackedUserOperation): UserOperation {
const [verificationGasLimit, callGasLimit] = unpackUint(packed.accountGasLimits)
const [maxPriorityFeePerGas, maxFeePerGas] = unpackUint(packed.gasFees)
let ret: UserOperation = {
sender: packed.sender,
nonce: packed.nonce,
callData: packed.callData,
preVerificationGas: packed.preVerificationGas,
verificationGasLimit,
callGasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
signature: packed.signature
}
if (packed.initCode != null && packed.initCode.length > 2) {
const factory = hexDataSlice(packed.initCode, 0, 20)
const factoryData = hexDataSlice(packed.initCode, 20)
ret = {
...ret,
factory,
factoryData
}
}
const pmData = unpackPaymasterAndData(packed.paymasterAndData)
if (pmData != null) {
ret = {
...ret,
paymaster: pmData.paymaster,
paymasterVerificationGasLimit: pmData.paymasterVerificationGas,
paymasterPostOpGasLimit: pmData.postOpGasLimit,
paymasterData: pmData.paymasterData
}
}
return ret
}
/**
* abi-encode the userOperation
* @param op a PackedUserOp
* @param forSignature "true" if the hash is needed to calculate the getUserOpHash()
* "false" to pack entire UserOp, for calculating the calldata cost of putting it on-chain.
*/
export function packUserOp (op: NotPromise<UserOperationStruct>, forSignature = true): string {
export function encodeUserOp (op1: PackedUserOperation | UserOperation, forSignature = true): string {
// if "op" is unpacked UserOperation, then pack it first, before we ABI-encode it.
let op: PackedUserOperation
if ('callGasLimit' in op1) {
op = packUserOp(op1)
} else {
op = op1
}
if (forSignature) {
return defaultAbiCoder.encode(
['address', 'uint256', 'bytes32', 'bytes32',
'uint256', 'uint256', 'uint256', 'uint256', 'uint256',
'bytes32', 'uint256', 'bytes32',
'bytes32'],
[op.sender, op.nonce, keccak256(op.initCode), keccak256(op.callData),
op.callGasLimit, op.verificationGasLimit, op.preVerificationGas, op.maxFeePerGas, op.maxPriorityFeePerGas,
op.accountGasLimits, op.preVerificationGas, op.gasFees,
keccak256(op.paymasterAndData)])
} else {
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
return defaultAbiCoder.encode(
['address', 'uint256', 'bytes', 'bytes',
'uint256', 'uint256', 'uint256', 'uint256', 'uint256',
'bytes32', 'uint256', 'bytes32',
'bytes', 'bytes'],
[op.sender, op.nonce, op.initCode, op.callData,
op.callGasLimit, op.verificationGasLimit, op.preVerificationGas, op.maxFeePerGas, op.maxPriorityFeePerGas,
op.accountGasLimits, op.preVerificationGas, op.gasFees,
op.paymasterAndData, op.signature])
}
}
@@ -56,8 +256,8 @@ export function packUserOp (op: NotPromise<UserOperationStruct>, forSignature =
* @param entryPoint
* @param chainId
*/
export function getUserOpHash (op: NotPromise<UserOperationStruct>, entryPoint: string, chainId: number): string {
const userOpHash = keccak256(packUserOp(op, true))
export function getUserOpHash (op: UserOperation, entryPoint: string, chainId: number): string {
const userOpHash = keccak256(encodeUserOp(op, true))
const enc = defaultAbiCoder.encode(
['bytes32', 'address', 'uint256'],
[userOpHash, entryPoint, chainId])
@@ -75,7 +275,12 @@ interface DecodedError {
/**
* decode bytes thrown by revert as Error(message) or FailedOp(opIndex,paymaster,message)
*/
export function decodeErrorReason (error: string): DecodedError | undefined {
export function decodeErrorReason (error: string | Error): DecodedError | undefined {
if (typeof error !== 'string') {
const err = error as any
error = (err.data ?? err.error.data) as string
}
debug('decoding', error)
if (error.startsWith(ErrorSig)) {
const [message] = defaultAbiCoder.decode(['string'], '0x' + error.substring(10))

View File

@@ -4,8 +4,8 @@ import { BytesLike, ContractFactory, BigNumber } from 'ethers'
import { hexlify, hexZeroPad, Result } from 'ethers/lib/utils'
import { Provider, JsonRpcProvider } from '@ethersproject/providers'
import { BigNumberish } from 'ethers/lib/ethers'
import { NotPromise } from './ERC4337Utils'
import { UserOperationStruct } from '@account-abstraction/contracts'
import { NotPromise, UserOperation } from './ERC4337Utils'
import { PackedUserOperationStruct } from './soltypes'
export interface SlotMap {
[slot: string]: string
@@ -25,7 +25,7 @@ export interface StakeInfo {
unstakeDelaySec: BigNumberish
}
export type UserOperation = NotPromise<UserOperationStruct>
export type PackedUserOperation = NotPromise<PackedUserOperationStruct>
export enum ValidationErrors {
InvalidFields = -32602,
@@ -65,6 +65,24 @@ export function requireCond (cond: boolean, msg: string, code?: number, data: an
}
}
// verify that either address field exist along with "mustFields",
// or address field is missing, and none of the must (or optional) field also exists
export function requireAddressAndFields (userOp: UserOperation, addrField: string, mustFields: string[], optionalFields: string[] = []): void {
const op = userOp as any
const addr = op[addrField]
if (addr == null) {
const unexpected = Object.entries(op)
.filter(([name, value]) => value != null && (mustFields.includes(name) || optionalFields.includes(name)))
requireCond(unexpected.length === 0,
`no ${addrField} but got ${unexpected.join(',')}`, ValidationErrors.InvalidFields)
} else {
requireCond(addr.match(/^0x[a-f0-9]{10,40}$/i), `invalid ${addrField}`, ValidationErrors.InvalidFields)
const missing = mustFields.filter(name => op[name] == null)
requireCond(missing.length === 0,
`got ${addrField} but missing ${missing.join(',')}`, ValidationErrors.InvalidFields)
}
}
/**
* create a dictionary object with given keys
* @param keys the property names of the returned object

View File

@@ -0,0 +1,70 @@
import { Interface } from '@ethersproject/abi'
import { ethers } from 'ethers'
import { EntryPointSimulations__factory, IPaymaster__factory } from './types'
import { SimpleAccount__factory } from './soltypes'
const decodeRevertReasonContracts = new Interface([
...EntryPointSimulations__factory.createInterface().fragments,
...IPaymaster__factory.createInterface().fragments,
...SimpleAccount__factory.createInterface().fragments
].filter((f: any) => f.type === 'error'))
/**
* helper to decode revert data into its string representation
* @param data revert data or an exception thrown by eth_call
* @param nullIfNoMatch true to return null if not found. otherwise, return input data as-is
*/
export function decodeRevertReason (data: string | Error, nullIfNoMatch = true): string | null {
if (typeof data !== 'string') {
const err = data as any
data = (err.data?.data ?? err.data ?? err.error.data) as string
}
const methodSig = data.slice(0, 10)
const dataParams = '0x' + data.slice(10)
try {
// would be nice to add these to above "decodeRevertReasonContracts", but we can't add Error(string) to xface...
if (methodSig === '0x08c379a0') {
const [err] = ethers.utils.defaultAbiCoder.decode(['string'], dataParams)
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
return `Error(${err})`
} else if (methodSig === '0x4e487b71') {
const [code] = ethers.utils.defaultAbiCoder.decode(['uint256'], dataParams)
return `Panic(${panicCodes[code] ?? code} + ')`
}
const err = decodeRevertReasonContracts.parseError(data)
// treat any error "bytes" argument as possible error to decode (e.g. FailedOpWithRevert, PostOpReverted)
const args = err.args.map((arg: any, index) => {
switch (err.errorFragment.inputs[index].type) {
case 'bytes' : return decodeRevertReason(arg, false)
case 'string': return `"${(arg as string)}"`
default: return arg
}
})
return `${err.name}(${args.join(',')})`
} catch (e) {
// throw new Error('unsupported errorSig ' + data)
if (!nullIfNoMatch) {
return data
}
return null
}
}
// not sure why ethers fail to decode revert reasons, not even "Error()" (and obviously, not custom errors)
export function rethrowWithRevertReason (e: Error): never {
throw new Error(decodeRevertReason(e, false) as any)
}
const panicCodes: { [key: number]: string } = {
// from https://docs.soliditylang.org/en/v0.8.0/control-structures.html
0x01: 'assert(false)',
0x11: 'arithmetic overflow/underflow',
0x12: 'divide by zero',
0x21: 'invalid enum value',
0x22: 'storage byte array that is incorrectly encoded',
0x31: '.pop() on an empty array.',
0x32: 'array sout-of-bounds or negative index',
0x41: 'memory overflow',
0x51: 'zero-initialized variable of internal function type'
}

View File

@@ -0,0 +1,15 @@
import { JsonRpcProvider } from '@ethersproject/providers'
import { bytecode as entryPointByteCode } from '@account-abstraction/contracts/artifacts/EntryPoint.json'
import { IEntryPoint, IEntryPoint__factory } from './soltypes'
import { DeterministicDeployer } from './DeterministicDeployer'
export const entryPointSalt = '0x90d8084deab30c2a37c45e8d47f49f2f7965183cb6990a98943ef94940681de3'
export async function deployEntryPoint (provider: JsonRpcProvider, signer = provider.getSigner()): Promise<IEntryPoint> {
const addr = await new DeterministicDeployer(provider, signer).deterministicDeploy(entryPointByteCode, entryPointSalt)
return IEntryPoint__factory.connect(addr, signer)
}
export function getEntryPointAddress (): string {
return DeterministicDeployer.getAddress(entryPointByteCode, entryPointSalt)
}

View File

@@ -1,4 +1,10 @@
export { UserOperation } from './ERC4337Utils'
export * from './Version'
export * from './decodeRevertReason'
export * from './ERC4337Utils'
export * from './DeterministicDeployer'
export * from './deployEntryPoint'
export * from './Utils'
export * from './types/index'
export * from './rpcSimulateValidations'
export * from './soltypes'

View File

@@ -1,11 +1,11 @@
import { resolveProperties } from 'ethers/lib/utils'
import { NotPromise } from './ERC4337Utils'
import { EntryPoint, UserOperationStruct } from '@account-abstraction/contracts'
import Debug from 'debug'
import { IEntryPoint } from './types'
import { PackedUserOperation } from './Utils'
const debug = Debug('aa.postExec')
export async function postExecutionDump (entryPoint: EntryPoint, userOpHash: string): Promise<void> {
export async function postExecutionDump (entryPoint: IEntryPoint, userOpHash: string): Promise<void> {
const { gasPaid, gasUsed, success, userOp } = await postExecutionCheck(entryPoint, userOpHash)
/// / debug dump:
debug('==== used=', gasUsed, 'paid', gasPaid, 'over=', gasPaid - gasUsed,
@@ -20,11 +20,11 @@ export async function postExecutionDump (entryPoint: EntryPoint, userOpHash: str
* @param entryPoint
* @param userOpHash
*/
export async function postExecutionCheck (entryPoint: EntryPoint, userOpHash: string): Promise<{
export async function postExecutionCheck (entryPoint: IEntryPoint, userOpHash: string): Promise<{
gasUsed: number
gasPaid: number
success: boolean
userOp: NotPromise<UserOperationStruct>
userOp: PackedUserOperation
}> {
const req = await entryPoint.queryFilter(entryPoint.filters.UserOperationEvent(userOpHash))
if (req.length === 0) {
@@ -36,7 +36,7 @@ export async function postExecutionCheck (entryPoint: EntryPoint, userOpHash: st
const tx = await req[0].getTransaction()
const { ops } = entryPoint.interface.decodeFunctionData('handleOps', tx.data)
const userOp = await resolveProperties(ops[0] as UserOperationStruct)
const userOp = await resolveProperties(ops[0] as PackedUserOperation)
const {
actualGasUsed,
success

View File

@@ -0,0 +1,40 @@
import { packUserOp, UserOperation } from './ERC4337Utils'
import EntryPointSimulationsJson from '@account-abstraction/contracts/artifacts/EntryPointSimulations.json'
import { EntryPointSimulations__factory, IEntryPointSimulations } from './types'
export const entryPointSimulationsInterface = EntryPointSimulations__factory.createInterface()
/**
* create the rpc params for eth_call (or debug_traceCall) for simulation method
* @param methodName the EntryPointSimulations method (simulateValidation or simulateHandleOp)
* @param entryPointAddress
* @param userOp
* @param extraOptions optional added tracer settings
*/
export function simulationRpcParams (methodName: string, entryPointAddress: string, userOp: UserOperation, extraParams: any[] = [], extraOptions: any = {}): any[] {
const data = entryPointSimulationsInterface.encodeFunctionData(methodName as any, [packUserOp(userOp), ...extraParams] as any)
const tx = {
to: entryPointAddress,
data
}
const stateOverride = {
[entryPointAddress]: {
code: EntryPointSimulationsJson.deployedBytecode
}
}
return [
tx,
'latest',
{
...extraOptions,
...stateOverride
}
]
}
export type SimulateHandleUpResult = IEntryPointSimulations.ExecutionResultStructOutput
export function decodeSimulateHandleOpResult (data: string): SimulateHandleUpResult {
return entryPointSimulationsInterface.decodeFunctionResult('simulateHandleOp', data)[0] as SimulateHandleUpResult
}

View File

@@ -0,0 +1,30 @@
import {
IEntryPointSimulations,
IStakeManager
} from './types/@account-abstraction/contracts/interfaces/IEntryPointSimulations'
export { PackedUserOperationStruct } from './types/@account-abstraction/contracts/core/EntryPoint'
export {
IAccount, IAccount__factory,
IEntryPoint, IEntryPoint__factory,
IStakeManager, IStakeManager__factory,
IPaymaster, IPaymaster__factory,
IEntryPointSimulations, IEntryPointSimulations__factory,
SenderCreator__factory,
CodeHashGetter__factory,
SampleRecipient, SampleRecipient__factory,
SimpleAccount, SimpleAccount__factory,
SimpleAccountFactory, SimpleAccountFactory__factory
} from './types'
export { TypedEvent } from './types/common'
export {
AccountDeployedEvent,
SignatureAggregatorChangedEvent,
UserOperationEventEvent
} from './types/@account-abstraction/contracts/interfaces/IEntryPoint'
export type ValidationResultStructOutput = IEntryPointSimulations.ValidationResultStructOutput
export type ExecutionResultStructOutput = IEntryPointSimulations.ExecutionResultStructOutput
export type StakeInfoStructOutput = IStakeManager.StakeInfoStructOutput

View File

@@ -1,6 +1,6 @@
{
"name": "@account-abstraction/validation-manager",
"version": "0.6.0",
"version": "0.7.0",
"main": "./dist/src/index.js",
"license": "MIT",
"files": [
@@ -16,9 +16,12 @@
"watch-tsc": "tsc -w --preserveWatchOutput"
},
"dependencies": {
"@account-abstraction/sdk": "0.6.0",
"@account-abstraction/utils": "0.6.0"
"@account-abstraction/sdk": "0.7.0",
"@account-abstraction/utils": "0.7.0",
"@ethersproject/properties": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"debug": "^4.3.4",
"ethers": "^5.7.0"
},
"devDependencies": {
}
"devDependencies": {}
}

View File

@@ -85,6 +85,7 @@ export interface TraceOptions {
enableReturnData?: boolean // Setting this to true will enable return data capture (default = false).
tracer?: LogTracerFunc | string // Setting this will enable JavaScript-based transaction tracing, described below. If set, the previous four arguments will be ignored.
timeout?: string // Overrides the default timeout of 5 seconds for JavaScript-based tracing calls. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
stateOverrides?: any
}
// the result type of debug_traceCall and debug_traceTransaction

View File

@@ -6,13 +6,6 @@ import { BigNumber, BigNumberish } from 'ethers'
import { hexZeroPad, Interface, keccak256 } from 'ethers/lib/utils'
import { inspect } from 'util'
import {
IAccount__factory,
IEntryPoint,
IEntryPoint__factory,
IPaymaster__factory,
SenderCreator__factory
} from '@account-abstraction/contracts'
import { BundlerTracerResult } from './BundlerCollectorTracer'
import {
StakeInfo,
@@ -21,7 +14,7 @@ import {
ValidationErrors,
mapOf,
requireCond,
toBytes32
toBytes32, SenderCreator__factory, IEntryPoint__factory, IPaymaster__factory, IAccount__factory, IEntryPoint
} from '@account-abstraction/utils'
import { ValidationResult } from './ValidationManager'
@@ -57,7 +50,6 @@ const abi = Object.values([
* - entries are ordered by the return (so nested call appears before its outer call
* - last entry is top-level return from "simulateValidation". it as ret and rettype, but no type or address
* @param tracerResults
* @param abi
*/
function parseCallStack (
tracerResults: BundlerTracerResult
@@ -317,7 +309,7 @@ export function tracerResultParser (
// slot associated with sender is allowed (e.g. token.balanceOf(sender)
// but during initial UserOp (where there is an initCode), it is allowed only for staked entity
if (associatedWith(slot, sender, entitySlots)) {
if (userOp.initCode.length > 2) {
if (userOp.factory != null) {
// special case: account.validateUserOp is allowed to use assoc storage if factory is staked.
// [STO-022], [STO-021]
if (!(entityAddr === sender && isStaked(stakeInfoEntities.factory))) {

View File

@@ -1,8 +1,8 @@
import { BigNumber, BigNumberish, BytesLike, ethers } from 'ethers'
import { BigNumber, BigNumberish } from 'ethers'
import { JsonRpcProvider } from '@ethersproject/providers'
import Debug from 'debug'
import { IEntryPoint } from '@account-abstraction/contracts'
import {
AddressZero,
CodeHashGetter__factory,
@@ -15,7 +15,15 @@ import {
decodeErrorReason,
getAddr,
requireCond,
runContractScript
runContractScript,
packUserOp,
requireAddressAndFields,
decodeRevertReason,
mergeValidationDataValues,
IEntryPointSimulations__factory,
IEntryPoint,
ValidationResultStructOutput,
StakeInfoStructOutput
} from '@account-abstraction/utils'
import { calcPreVerificationGas } from '@account-abstraction/sdk'
@@ -23,13 +31,15 @@ import { tracerResultParser } from './TracerResultParser'
import { BundlerTracerResult, bundlerCollectorTracer, ExitInfo } from './BundlerCollectorTracer'
import { debug_traceCall } from './GethTracer'
import EntryPointSimulationsJson from '@account-abstraction/contracts/artifacts/EntryPointSimulations.json'
const debug = Debug('aa.mgr.validate')
// how much time into the future a UserOperation must be valid in order to be accepted
const VALID_UNTIL_FUTURE_SECONDS = 30
/**
* result from successful simulateValidation
* result from successful simulateValidation, after some parsing.
*/
export interface ValidationResult {
returnInfo: {
@@ -53,122 +63,125 @@ export interface ValidateUserOpResult extends ValidationResult {
}
const HEX_REGEX = /^0x[a-fA-F\d]*$/i
const entryPointSimulations = IEntryPointSimulations__factory.createInterface()
export class ValidationManager {
constructor (
readonly entryPoint: IEntryPoint,
readonly unsafe: boolean
) {}
) {
}
parseValidationResult (userOp: UserOperation, res: ValidationResultStructOutput): ValidationResult {
const mergedValidation = mergeValidationDataValues(res.returnInfo.accountValidationData, res.returnInfo.paymasterValidationData)
function fillEntity (addr: string | undefined, info: StakeInfoStructOutput): StakeInfo | undefined {
if (addr == null || addr === AddressZero) return undefined
return {
addr,
stake: info.stake,
unstakeDelaySec: info.unstakeDelaySec
}
}
const returnInfo = {
sigFailed: mergedValidation.aggregator !== AddressZero,
validUntil: mergedValidation.validUntil,
validAfter: mergedValidation.validAfter,
preOpGas: res.returnInfo.preOpGas,
prefund: res.returnInfo.prefund
}
return {
returnInfo,
senderInfo: fillEntity(userOp.sender, res.senderInfo) as StakeInfo,
paymasterInfo: fillEntity(userOp.paymaster, res.paymasterInfo),
factoryInfo: fillEntity(userOp.factory, res.factoryInfo),
aggregatorInfo: fillEntity(res.aggregatorInfo.aggregator, res.aggregatorInfo.stakeInfo)
}
}
// standard eth_call to simulateValidation
async _callSimulateValidation (userOp: UserOperation): Promise<ValidationResult> {
const errorResult = await this.entryPoint.callStatic.simulateValidation(userOp, { gasLimit: 10e6 }).catch(e => e)
return this._parseErrorResult(userOp, errorResult)
// Promise<IEntryPointSimulations.ValidationResultStructOutput> {
const data = entryPointSimulations.encodeFunctionData('simulateValidation', [packUserOp(userOp)])
const tx = {
to: this.entryPoint.address,
data
}
const stateOverride = {
[this.entryPoint.address]: {
code: EntryPointSimulationsJson.deployedBytecode
}
}
try {
const provider = this.entryPoint.provider as JsonRpcProvider
const simulationResult = await provider.send('eth_call', [tx, 'latest', stateOverride])
const [res] = entryPointSimulations.decodeFunctionResult('simulateValidation', simulationResult) as ValidationResultStructOutput[]
return this.parseValidationResult(userOp, res)
} catch (error: any) {
const decodedError = decodeRevertReason(error)
if (decodedError != null) {
throw new RpcError(decodedError, ValidationErrors.SimulateValidation)
}
throw error
}
}
_parseErrorResult (userOp: UserOperation, errorResult: { errorName: string, errorArgs: any }): ValidationResult {
if (!errorResult?.errorName?.startsWith('ValidationResult')) {
// parse it as FailedOp
// if its FailedOp, then we have the paymaster param... otherwise its an Error(string)
let paymaster = errorResult.errorArgs.paymaster
if (paymaster === AddressZero) {
paymaster = undefined
}
// eslint-disable-next-line
const msg: string = errorResult.errorArgs?.reason ?? errorResult.toString()
if (paymaster == null) {
throw new RpcError(`account validation failed: ${msg}`, ValidationErrors.SimulateValidation)
} else {
throw new RpcError(`paymaster validation failed: ${msg}`, ValidationErrors.SimulatePaymasterValidation, { paymaster })
}
}
const {
returnInfo,
senderInfo,
factoryInfo,
paymasterInfo,
aggregatorInfo // may be missing (exists only SimulationResultWithAggregator
} = errorResult.errorArgs
// extract address from "data" (first 20 bytes)
// add it as "addr" member to the "stakeinfo" struct
// if no address, then return "undefined" instead of struct.
function fillEntity (data: BytesLike, info: StakeInfo): StakeInfo | undefined {
const addr = getAddr(data)
return addr == null
? undefined
: {
...info,
addr
}
}
return {
returnInfo,
senderInfo: {
...senderInfo,
addr: userOp.sender
},
factoryInfo: fillEntity(userOp.initCode, factoryInfo),
paymasterInfo: fillEntity(userOp.paymasterAndData, paymasterInfo),
aggregatorInfo: fillEntity(aggregatorInfo?.actualAggregator, aggregatorInfo?.stakeInfo)
}
// decode and throw error
_throwError (errorResult: { errorName: string, errorArgs: any }): never {
throw new Error(errorResult.errorName)
}
async _geth_traceCall_SimulateValidation (userOp: UserOperation): Promise<[ValidationResult, BundlerTracerResult]> {
const provider = this.entryPoint.provider as JsonRpcProvider
const simulateCall = this.entryPoint.interface.encodeFunctionData('simulateValidation', [userOp])
const simulateCall = entryPointSimulations.encodeFunctionData('simulateValidation', [packUserOp(userOp)])
const simulationGas = BigNumber.from(userOp.preVerificationGas).add(userOp.verificationGasLimit)
const tracerResult: BundlerTracerResult = await debug_traceCall(provider, {
from: ethers.constants.AddressZero,
from: AddressZero,
to: this.entryPoint.address,
data: simulateCall,
gasLimit: simulationGas
}, { tracer: bundlerCollectorTracer })
}, {
tracer: bundlerCollectorTracer,
stateOverrides: {
[this.entryPoint.address]: {
code: EntryPointSimulationsJson.deployedBytecode
}
}
})
const lastResult = tracerResult.calls.slice(-1)[0]
if (lastResult.type !== 'REVERT') {
throw new Error('Invalid response. simulateCall must revert')
}
const data = (lastResult as ExitInfo).data
// Hack to handle SELFDESTRUCT until we fix entrypoint
if (data === '0x') {
return [data as any, tracerResult]
if (lastResult.type === 'REVERT') {
throw new RpcError(decodeRevertReason(data, false) as string, ValidationErrors.SimulateValidation)
}
// // Hack to handle SELFDESTRUCT until we fix entrypoint
// if (data === '0x') {
// return [data as any, tracerResult]
// }
try {
const {
name: errorName,
args: errorArgs
} = this.entryPoint.interface.parseError(data)
const errFullName = `${errorName}(${errorArgs.toString()})`
const errorResult = this._parseErrorResult(userOp, {
errorName,
errorArgs
})
if (!errorName.includes('Result')) {
// a real error, not a result.
throw new Error(errFullName)
}
const [decodedSimulations] = entryPointSimulations.decodeFunctionResult('simulateValidation', data)
const validationResult = this.parseValidationResult(userOp, decodedSimulations)
debug('==dump tree=', JSON.stringify(tracerResult, null, 2)
.replace(new RegExp(userOp.sender.toLowerCase()), '{sender}')
.replace(new RegExp(getAddr(userOp.paymasterAndData) ?? '--no-paymaster--'), '{paymaster}')
.replace(new RegExp(getAddr(userOp.initCode) ?? '--no-initcode--'), '{factory}')
.replace(new RegExp(getAddr(userOp.paymaster) ?? '--no-paymaster--'), '{paymaster}')
.replace(new RegExp(getAddr(userOp.factory) ?? '--no-initcode--'), '{factory}')
)
// console.log('==debug=', ...tracerResult.numberLevels.forEach(x=>x.access), 'sender=', userOp.sender, 'paymaster=', hexlify(userOp.paymasterAndData)?.slice(0, 42))
// errorResult is "ValidationResult"
return [errorResult, tracerResult]
return [validationResult, tracerResult]
} catch (e: any) {
// if already parsed, throw as is
if (e.code != null) {
throw e
}
// not a known error of EntryPoint (probably, only Error(string), since FailedOp is handled above)
const err = decodeErrorReason(data)
throw new RpcError(err != null ? err.message : data, 111)
const err = decodeErrorReason(e)
throw new RpcError(err != null ? err.message : data, -32000)
}
}
@@ -194,7 +207,9 @@ export class ValidationManager {
let storageMap: StorageMap = {}
if (!this.unsafe) {
let tracerResult: BundlerTracerResult
[res, tracerResult] = await this._geth_traceCall_SimulateValidation(userOp)
[res, tracerResult] = await this._geth_traceCall_SimulateValidation(userOp).catch(e => {
throw e
})
let contractAddresses: string[]
[contractAddresses, storageMap] = tracerResultParser(userOp, tracerResult, res, this.entryPoint)
// if no previous contract hashes, then calculate hashes of contracts
@@ -215,7 +230,7 @@ export class ValidationManager {
const now = Math.floor(Date.now() / 1000)
requireCond(res.returnInfo.validAfter <= now,
'time-range in the future time',
`time-range in the future time ${res.returnInfo.validAfter}, now=${now}`,
ValidationErrors.NotInTimeRange)
requireCond(res.returnInfo.validUntil == null || res.returnInfo.validUntil >= now,
@@ -270,7 +285,7 @@ export class ValidationManager {
// minimal sanity check: userOp exists, and all members are hex
requireCond(userOp != null, 'No UserOperation param', ValidationErrors.InvalidFields)
const fields = ['sender', 'nonce', 'initCode', 'callData', 'paymasterAndData']
const fields = ['sender', 'nonce', 'callData']
if (requireSignature) {
fields.push('signature')
}
@@ -287,17 +302,11 @@ export class ValidationManager {
ValidationErrors.InvalidFields)
})
requireCond(userOp.paymasterAndData.length === 2 || userOp.paymasterAndData.length >= 42,
'paymasterAndData: must contain at least an address',
ValidationErrors.InvalidFields)
// syntactically, initCode can be only the deployer address. but in reality, it must have calldata to uniquely identify the account
requireCond(userOp.initCode.length === 2 || userOp.initCode.length >= 42,
'initCode: must contain at least an address',
ValidationErrors.InvalidFields)
requireAddressAndFields(userOp, 'paymaster', ['paymasterPostOpGasLimit', 'paymasterVerificationGasLimit'], ['paymasterData'])
requireAddressAndFields(userOp, 'factory', ['factoryData'])
const calcPreVerificationGas1 = calcPreVerificationGas(userOp)
requireCond(userOp.preVerificationGas >= calcPreVerificationGas1,
requireCond(BigNumber.from(userOp.preVerificationGas).gte(calcPreVerificationGas1),
`preVerificationGas too low: expected at least ${calcPreVerificationGas1}`,
ValidationErrors.InvalidFields)
}

250
yarn.lock
View File

@@ -7,11 +7,6 @@
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
"@account-abstraction/contracts@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@account-abstraction/contracts/-/contracts-0.6.0.tgz#7188a01839999226e6b2796328af338329543b76"
integrity sha512-8ooRJuR7XzohMDM4MV34I12Ci2bmxfE9+cixakRL7lA4BAwJKQ3ahvd8FbJa9kiwkUPCUNtj+/zxDQWYYalLMQ==
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
@@ -1403,31 +1398,31 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@nomicfoundation/ethereumjs-block@5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz#6f89664f55febbd723195b6d0974773d29ee133d"
integrity sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==
"@nomicfoundation/ethereumjs-block@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.2.tgz#13a7968f5964f1697da941281b7f7943b0465d04"
integrity sha512-hSe6CuHI4SsSiWWjHDIzWhSiAVpzMUcDRpWYzN0T9l8/Rz7xNn3elwVOJ/tAyS0LqL6vitUD78Uk7lQDXZun7Q==
dependencies:
"@nomicfoundation/ethereumjs-common" "4.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-trie" "6.0.1"
"@nomicfoundation/ethereumjs-tx" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-common" "4.0.2"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
"@nomicfoundation/ethereumjs-trie" "6.0.2"
"@nomicfoundation/ethereumjs-tx" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
ethereum-cryptography "0.1.3"
ethers "^5.7.1"
"@nomicfoundation/ethereumjs-blockchain@7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz#80e0bd3535bfeb9baa29836b6f25123dab06a726"
integrity sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==
"@nomicfoundation/ethereumjs-blockchain@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.2.tgz#45323b673b3d2fab6b5008535340d1b8fea7d446"
integrity sha512-8UUsSXJs+MFfIIAKdh3cG16iNmWzWC/91P40sazNvrqhhdR/RtGDlFk2iFTGbBAZPs2+klZVzhRX8m2wvuvz3w==
dependencies:
"@nomicfoundation/ethereumjs-block" "5.0.1"
"@nomicfoundation/ethereumjs-common" "4.0.1"
"@nomicfoundation/ethereumjs-ethash" "3.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-trie" "6.0.1"
"@nomicfoundation/ethereumjs-tx" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-block" "5.0.2"
"@nomicfoundation/ethereumjs-common" "4.0.2"
"@nomicfoundation/ethereumjs-ethash" "3.0.2"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
"@nomicfoundation/ethereumjs-trie" "6.0.2"
"@nomicfoundation/ethereumjs-tx" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
abstract-level "^1.0.3"
debug "^4.3.3"
ethereum-cryptography "0.1.3"
@@ -1435,103 +1430,103 @@
lru-cache "^5.1.1"
memory-level "^1.0.0"
"@nomicfoundation/ethereumjs-common@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz#4702d82df35b07b5407583b54a45bf728e46a2f0"
integrity sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==
"@nomicfoundation/ethereumjs-common@4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.2.tgz#a15d1651ca36757588fdaf2a7d381a150662a3c3"
integrity sha512-I2WGP3HMGsOoycSdOTSqIaES0ughQTueOsddJ36aYVpI3SN8YSusgRFLwzDJwRFVIYDKx/iJz0sQ5kBHVgdDwg==
dependencies:
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.2"
crc-32 "^1.2.0"
"@nomicfoundation/ethereumjs-ethash@3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz#65ca494d53e71e8415c9a49ef48bc921c538fc41"
integrity sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==
"@nomicfoundation/ethereumjs-ethash@3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.2.tgz#da77147f806401ee996bfddfa6487500118addca"
integrity sha512-8PfoOQCcIcO9Pylq0Buijuq/O73tmMVURK0OqdjhwqcGHYC2PwhbajDh7GZ55ekB0Px197ajK3PQhpKoiI/UPg==
dependencies:
"@nomicfoundation/ethereumjs-block" "5.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-block" "5.0.2"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
abstract-level "^1.0.3"
bigint-crypto-utils "^3.0.23"
ethereum-cryptography "0.1.3"
"@nomicfoundation/ethereumjs-evm@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz#f35681e203363f69ce2b3d3bf9f44d4e883ca1f1"
integrity sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==
"@nomicfoundation/ethereumjs-evm@2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.2.tgz#4c2f4b84c056047102a4fa41c127454e3f0cfcf6"
integrity sha512-rBLcUaUfANJxyOx9HIdMX6uXGin6lANCulIm/pjMgRqfiCRMZie3WKYxTSd8ZE/d+qT+zTedBF4+VHTdTSePmQ==
dependencies:
"@ethersproject/providers" "^5.7.1"
"@nomicfoundation/ethereumjs-common" "4.0.1"
"@nomicfoundation/ethereumjs-tx" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-common" "4.0.2"
"@nomicfoundation/ethereumjs-tx" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
debug "^4.3.3"
ethereum-cryptography "0.1.3"
mcl-wasm "^0.7.1"
rustbn.js "~0.2.0"
"@nomicfoundation/ethereumjs-rlp@5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz#0b30c1cf77d125d390408e391c4bb5291ef43c28"
integrity sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==
"@nomicfoundation/ethereumjs-rlp@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.2.tgz#4fee8dc58a53ac6ae87fb1fca7c15dc06c6b5dea"
integrity sha512-QwmemBc+MMsHJ1P1QvPl8R8p2aPvvVcKBbvHnQOKBpBztEo0omN0eaob6FeZS/e3y9NSe+mfu3nNFBHszqkjTA==
"@nomicfoundation/ethereumjs-statemanager@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz#8824a97938db4471911e2d2f140f79195def5935"
integrity sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==
"@nomicfoundation/ethereumjs-statemanager@2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.2.tgz#3ba4253b29b1211cafe4f9265fee5a0d780976e0"
integrity sha512-dlKy5dIXLuDubx8Z74sipciZnJTRSV/uHG48RSijhgm1V7eXYFC567xgKtsKiVZB1ViTP9iFL4B6Je0xD6X2OA==
dependencies:
"@nomicfoundation/ethereumjs-common" "4.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-common" "4.0.2"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
debug "^4.3.3"
ethereum-cryptography "0.1.3"
ethers "^5.7.1"
js-sdsl "^4.1.4"
"@nomicfoundation/ethereumjs-trie@6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz#662c55f6b50659fd4b22ea9f806a7401cafb7717"
integrity sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==
"@nomicfoundation/ethereumjs-trie@6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.2.tgz#9a6dbd28482dca1bc162d12b3733acab8cd12835"
integrity sha512-yw8vg9hBeLYk4YNg5MrSJ5H55TLOv2FSWUTROtDtTMMmDGROsAu+0tBjiNGTnKRi400M6cEzoFfa89Fc5k8NTQ==
dependencies:
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
"@types/readable-stream" "^2.3.13"
ethereum-cryptography "0.1.3"
readable-stream "^3.6.0"
"@nomicfoundation/ethereumjs-tx@5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz#7629dc2036b4a33c34e9f0a592b43227ef4f0c7d"
integrity sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==
"@nomicfoundation/ethereumjs-tx@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.2.tgz#117813b69c0fdc14dd0446698a64be6df71d7e56"
integrity sha512-T+l4/MmTp7VhJeNloMkM+lPU3YMUaXdcXgTGCf8+ZFvV9NYZTRLFekRwlG6/JMmVfIfbrW+dRRJ9A6H5Q/Z64g==
dependencies:
"@chainsafe/ssz" "^0.9.2"
"@ethersproject/providers" "^5.7.2"
"@nomicfoundation/ethereumjs-common" "4.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-common" "4.0.2"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
ethereum-cryptography "0.1.3"
"@nomicfoundation/ethereumjs-util@9.0.1":
version "9.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz#530cda8bae33f8b5020a8f199ed1d0a2ce48ec89"
integrity sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==
"@nomicfoundation/ethereumjs-util@9.0.2":
version "9.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.2.tgz#16bdc1bb36f333b8a3559bbb4b17dac805ce904d"
integrity sha512-4Wu9D3LykbSBWZo8nJCnzVIYGvGCuyiYLIJa9XXNVt1q1jUzHdB+sJvx95VGCpPkCT+IbLecW6yfzy3E1bQrwQ==
dependencies:
"@chainsafe/ssz" "^0.10.0"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
ethereum-cryptography "0.1.3"
"@nomicfoundation/ethereumjs-vm@7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz#7d035e0993bcad10716c8b36e61dfb87fa3ca05f"
integrity sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==
"@nomicfoundation/ethereumjs-vm@7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.2.tgz#3b0852cb3584df0e18c182d0672a3596c9ca95e6"
integrity sha512-Bj3KZT64j54Tcwr7Qm/0jkeZXJMfdcAtRBedou+Hx0dPOSIgqaIr0vvLwP65TpHbak2DmAq+KJbW2KNtIoFwvA==
dependencies:
"@nomicfoundation/ethereumjs-block" "5.0.1"
"@nomicfoundation/ethereumjs-blockchain" "7.0.1"
"@nomicfoundation/ethereumjs-common" "4.0.1"
"@nomicfoundation/ethereumjs-evm" "2.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-statemanager" "2.0.1"
"@nomicfoundation/ethereumjs-trie" "6.0.1"
"@nomicfoundation/ethereumjs-tx" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-block" "5.0.2"
"@nomicfoundation/ethereumjs-blockchain" "7.0.2"
"@nomicfoundation/ethereumjs-common" "4.0.2"
"@nomicfoundation/ethereumjs-evm" "2.0.2"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
"@nomicfoundation/ethereumjs-statemanager" "2.0.2"
"@nomicfoundation/ethereumjs-trie" "6.0.2"
"@nomicfoundation/ethereumjs-tx" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
debug "^4.3.3"
ethereum-cryptography "0.1.3"
mcl-wasm "^0.7.1"
@@ -1974,10 +1969,15 @@
dependencies:
"@octokit/openapi-types" "^18.0.0"
"@openzeppelin/contracts@^4.7.3":
version "4.9.2"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1"
integrity sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg==
"@openzeppelin/contracts@3.4.2-solc-0.7":
version "3.4.2-solc-0.7"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635"
integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==
"@openzeppelin/contracts@^5.0.0", "@openzeppelin/contracts@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.1.tgz#93da90fc209a0a4ff09c1deb037fbb35e4020890"
integrity sha512-yQJaT5HDp9hYOOp4jTYxMsR02gdFZFXhewX5HW9Jo4fsqSVqqyIO/xTHdWDaKX5a3pv1txmf076Lziz+sO7L1w==
"@parcel/watcher@2.0.4":
version "2.0.4"
@@ -2564,6 +2564,32 @@
"@typescript-eslint/types" "5.62.0"
eslint-visitor-keys "^3.3.0"
"@uniswap/lib@^4.0.1-alpha":
version "4.0.1-alpha"
resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02"
integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==
"@uniswap/v2-core@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425"
integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==
"@uniswap/v3-core@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0"
integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==
"@uniswap/v3-periphery@^1.4.3":
version "1.4.4"
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz#d2756c23b69718173c5874f37fd4ad57d2f021b7"
integrity sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw==
dependencies:
"@openzeppelin/contracts" "3.4.2-solc-0.7"
"@uniswap/lib" "^4.0.1-alpha"
"@uniswap/v2-core" "^1.0.1"
"@uniswap/v3-core" "^1.0.0"
base64-sol "1.0.1"
"@vue/compiler-core@3.3.4":
version "3.3.4"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128"
@@ -2808,13 +2834,6 @@ abbrev@1.0.x:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
abortcontroller-polyfill@^1.7.3:
version "1.7.5"
resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed"
@@ -3263,6 +3282,11 @@ base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64-sol@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6"
integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
@@ -5314,11 +5338,6 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6:
is-hex-prefixed "1.0.0"
strip-hex-prefix "1.0.0"
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
eventemitter3@4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
@@ -6273,28 +6292,27 @@ hardhat-gas-reporter@^1.0.8:
eth-gas-reporter "^0.2.25"
sha1 "^1.1.1"
hardhat@^2.11.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.17.0.tgz#574478790fa4f4a45c5ccf162e82e54f36671749"
integrity sha512-CaEGa13tkJNe2/rdaBiive4pmdNShwxvdWVhr1zfb6aVpRhQt9VNO0l/UIBt/zzajz38ZFjvhfM2bj8LDXo9gw==
hardhat@^2.17.0:
version "2.19.4"
resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.19.4.tgz#5112c30295d8be2e18e55d847373c50483ed1902"
integrity sha512-fTQJpqSt3Xo9Mn/WrdblNGAfcANM6XC3tAEi6YogB4s02DmTf93A8QsGb8uR0KR8TFcpcS8lgiW4ugAIYpnbrQ==
dependencies:
"@ethersproject/abi" "^5.1.2"
"@metamask/eth-sig-util" "^4.0.0"
"@nomicfoundation/ethereumjs-block" "5.0.1"
"@nomicfoundation/ethereumjs-blockchain" "7.0.1"
"@nomicfoundation/ethereumjs-common" "4.0.1"
"@nomicfoundation/ethereumjs-evm" "2.0.1"
"@nomicfoundation/ethereumjs-rlp" "5.0.1"
"@nomicfoundation/ethereumjs-statemanager" "2.0.1"
"@nomicfoundation/ethereumjs-trie" "6.0.1"
"@nomicfoundation/ethereumjs-tx" "5.0.1"
"@nomicfoundation/ethereumjs-util" "9.0.1"
"@nomicfoundation/ethereumjs-vm" "7.0.1"
"@nomicfoundation/ethereumjs-block" "5.0.2"
"@nomicfoundation/ethereumjs-blockchain" "7.0.2"
"@nomicfoundation/ethereumjs-common" "4.0.2"
"@nomicfoundation/ethereumjs-evm" "2.0.2"
"@nomicfoundation/ethereumjs-rlp" "5.0.2"
"@nomicfoundation/ethereumjs-statemanager" "2.0.2"
"@nomicfoundation/ethereumjs-trie" "6.0.2"
"@nomicfoundation/ethereumjs-tx" "5.0.2"
"@nomicfoundation/ethereumjs-util" "9.0.2"
"@nomicfoundation/ethereumjs-vm" "7.0.2"
"@nomicfoundation/solidity-analyzer" "^0.1.0"
"@sentry/node" "^5.18.1"
"@types/bn.js" "^5.1.0"
"@types/lru-cache" "^5.1.0"
abort-controller "^3.0.0"
adm-zip "^0.4.16"
aggregate-error "^3.0.0"
ansi-escapes "^4.3.0"