mirror of
https://github.com/getwax/bundler.git
synced 2026-01-08 23:28:10 -05:00
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal 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
|
||||
6
.idea/jsLinters/eslint.xml
generated
6
.idea/jsLinters/eslint.xml
generated
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"gasFactor": "1",
|
||||
"port": "3000",
|
||||
"entryPoint": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
||||
"entryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
||||
"beneficiary": "0xd21934eD8eAf27a67f0A70042Af50A1D6d195E81",
|
||||
"minBalance": "1",
|
||||
"mnemonic": "./workdir/mnemonic.txt",
|
||||
|
||||
14
package.json
14
package.json
@@ -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",
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ?? '',
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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")'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
117
packages/sdk/test/0-utils.test.ts
Normal file
117
packages/sdk/test/0-utils.test.ts
Normal 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'
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
7
packages/utils/contracts/Imports.sol
Normal file
7
packages/utils/contracts/Imports.sol
Normal 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";
|
||||
@@ -9,7 +9,7 @@ const config: HardhatUserConfig = {
|
||||
target: 'ethers-v5'
|
||||
},
|
||||
solidity: {
|
||||
version: '0.8.15',
|
||||
version: '0.8.23',
|
||||
settings: {
|
||||
optimizer: { enabled: true }
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
70
packages/utils/src/decodeRevertReason.ts
Normal file
70
packages/utils/src/decodeRevertReason.ts
Normal 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'
|
||||
}
|
||||
15
packages/utils/src/deployEntryPoint.ts
Normal file
15
packages/utils/src/deployEntryPoint.ts
Normal 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)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
40
packages/utils/src/rpcSimulateValidations.ts
Normal file
40
packages/utils/src/rpcSimulateValidations.ts
Normal 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
|
||||
}
|
||||
30
packages/utils/src/soltypes.ts
Normal file
30
packages/utils/src/soltypes.ts
Normal 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
|
||||
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
1
submodules/account-abstraction
Submodule
1
submodules/account-abstraction
Submodule
Submodule submodules/account-abstraction added at c08507428a
250
yarn.lock
250
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user