pimlicolabs / alto Goto Github PK
View Code? Open in Web Editor NEWA performant, reliable, and type-safe ERC-4337 Bundler written in Typescript
Home Page: https://docs.pimlico.io/reference/bundler
License: GNU General Public License v3.0
A performant, reliable, and type-safe ERC-4337 Bundler written in Typescript
Home Page: https://docs.pimlico.io/reference/bundler
License: GNU General Public License v3.0
Describe the bug
When run on Tenderly forks, the Bundler's sendUserOperation requests fail with an opaque error and are not natively decoded by the schema validation logic of the Bundler
Describe the bug
Hello there
Right now I'm working on a tool, that could activate a huge amount of smart accounts derived from one deployer address. Basically I'm sending all of them without waiting for the transaction to be minted and then waiting for the userOp statuses to become included
into . But I faced an issue, where I have a lot of userOps in the state non_submited
, which means that they are in mempool waiting to be included in the bundle. And they do but only like 4-5 userOps at a time.
So I started researching how to increase a bandwidth and found those flags: max-gas-per-bundle
and mempool-max-parallel-ops
. After increasing default values of it -- nothing happened. And I tried to reduce it to the ridiculously low value of 5 which I assumed will make bundler to not include any transaction to the bundle. But nothing happened again, I had the same 4-5 userOps at a time.
So is there any problem with this argument, or an issue with the bandwidth is in something else?
To Reproduce
Try to send a lot of transactions to the bundler and then wait for them to be mined.
The way I used to start bundler:
node src/lib/cli/alto.js run "--port" "3000" "--network-name" "mainnet" "--entrypoints" "0x" "--executor-private-keys" "0x" "--utility-private-key" "0x" "--min-executor-balance" "1000000000000000000" "--rpc-url" "http://eth_node:8545" "--log-level" "info" "--safe-mode" "false" "--max-gas-per-bundle" "5" "--mempool-max-parallel-ops" "50"
Expected behavior
With a increase of max-gas-per-bundle
argument, amount of time for the processing of all userOps should be decreased
Environment:
Just heard about alto today so curious about how it differs from the existing ones.
Will look forward to explore more.
Mumbai and Polygon RPC gasprice estimations are incorrect. GasStattion is typically used for better estimation
The two urls to be used are:
https://gasstation-mainnet.matic.network/v2
https://gasstation-mumbai.matic.today/v2
Describe the bug
The process exits when refilling the wallet due to a "replacement transaction underpriced" error.
Expected behavior
Regardless of any transaction failures, the bundler shouldn't shut down.
Screenshots
{"level":30,"time":1697786179976,"pid":1061950,"hostname":"PublicSale","module":"executor","tx":"0xd5f39c3dd30e7275bc1e811df91070db9b2baaab4d03bb3ab2a4daffa1bb6499","executor":"0x072a207D1Ec56D4A60FdBaD6e72B605c6CAD60C2","missingBalance":"0x670aeafb69165bb40","msg":"refilled wallet"}
/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/utils/errors/getTransactionError.js:11
return new transaction_js_1.TransactionExecutionError(cause, {
^
TransactionExecutionError: Missing or invalid parameters.
Double check you have provided the correct parameters.
URL: http://**********:3334/
Request body: {"method":"eth_sendRawTransaction","params":["0x02f8788501823cf7e88202cd830f4248830f424882520894f1b4a63eaedce2283e33f2a6ed52005ea385ea15890670aeaf0994e6d34080c080a0bba113532b5b3c16796a8d329c8d708e2624d6596725d6a718ca721d83942266a05cd6b3f72fb70144e961b5a7936f4f6627f23edc66243fe1ec43ad0b2fa08a37"]}
Request Arguments:
from: 0xA949e80F95F27f64C7b7cE33711A43304c6aAAc4
to: 0xF1B4a63EAedcE2283e33f2a6ed52005eA385ea15
value: 118.800084076140614464 ETH
maxFeePerGas: 0.001000008 gwei
maxPriorityFeePerGas: 0.001000008 gwei
Details: replacement transaction underpriced
Version: [email protected]
at getTransactionError (/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/utils/errors/getTransactionError.js:11:12)
at sendTransaction (/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/actions/wallet/sendTransaction.js:77:64)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async SenderManager.validateAndRefillWallets (/home/testnet/endurance/aa/alto-priv/packages/executor/lib/senderManager.js:108:32)
at async Timeout._onTimeout (/home/testnet/endurance/aa/alto-priv/packages/cli/lib/handler.js:227:9) {
details: 'replacement transaction underpriced',
docsPath: undefined,
metaMessages: [
'URL: http://**********:3334/',
'Request body: {"method":"eth_sendRawTransaction","params":["0x02f8788501823cf7e88202cd830f4248830f424882520894f1b4a63eaedce2283e33f2a6ed52005ea385ea15890670aeaf0994e6d34080c080a0bba113532b5b3c16796a8d329c8d708e2624d6596725d6a718ca721d83942266a05cd6b3f72fb70144e961b5a7936f4f6627f23edc66243fe1ec43ad0b2fa08a37"]}',
' ',
'Request Arguments:',
' from: 0xA949e80F95F27f64C7b7cE33711A43304c6aAAc4\n' +
' to: 0xF1B4a63EAedcE2283e33f2a6ed52005eA385ea15\n' +
' value: 118.800084076140614464 ETH\n' +
' maxFeePerGas: 0.001000008 gwei\n' +
' maxPriorityFeePerGas: 0.001000008 gwei'
],
shortMessage: 'Missing or invalid parameters.\n' +
'Double check you have provided the correct parameters.',
version: '[email protected]',
cause: InvalidInputRpcError: Missing or invalid parameters.
Double check you have provided the correct parameters.
URL: http://**********:3334/
Request body: {"method":"eth_sendRawTransaction","params":["0x02f8788501823cf7e88202cd830f4248830f424882520894f1b4a63eaedce2283e33f2a6ed52005ea385ea15890670aeaf0994e6d34080c080a0bba113532b5b3c16796a8d329c8d708e2624d6596725d6a718ca721d83942266a05cd6b3f72fb70144e961b5a7936f4f6627f23edc66243fe1ec43ad0b2fa08a37"]}
Details: replacement transaction underpriced
Version: [email protected]
at delay.count.count (/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/utils/buildRequest.js:46:27)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async attemptRetry (/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/utils/promise/withRetry.js:15:30) {
details: 'replacement transaction underpriced',
docsPath: undefined,
metaMessages: [
'URL: http://**********:3334/',
'Request body: {"method":"eth_sendRawTransaction","params":["0x02f8788501823cf7e88202cd830f4248830f424882520894f1b4a63eaedce2283e33f2a6ed52005ea385ea15890670aeaf0994e6d34080c080a0bba113532b5b3c16796a8d329c8d708e2624d6596725d6a718ca721d83942266a05cd6b3f72fb70144e961b5a7936f4f6627f23edc66243fe1ec43ad0b2fa08a37"]}'
],
shortMessage: 'Missing or invalid parameters.\n' +
'Double check you have provided the correct parameters.',
version: '[email protected]',
cause: RpcRequestError: RPC Request failed.
URL: http://**********:3334/
Request body: {"method":"eth_sendRawTransaction","params":["0x02f8788501823cf7e88202cd830f4248830f424882520894f1b4a63eaedce2283e33f2a6ed52005ea385ea15890670aeaf0994e6d34080c080a0bba113532b5b3c16796a8d329c8d708e2624d6596725d6a718ca721d83942266a05cd6b3f72fb70144e961b5a7936f4f6627f23edc66243fe1ec43ad0b2fa08a37"]}
Details: replacement transaction underpriced
Version: [email protected]
at request (/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/clients/transports/http.js:40:27)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async delay.count.count (/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/utils/buildRequest.js:30:20)
at async attemptRetry (/home/testnet/endurance/aa/alto-priv/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/dist/cjs/utils/promise/withRetry.js:15:30) {
details: 'replacement transaction underpriced',
docsPath: undefined,
metaMessages: [
'URL: http://**********:3334/',
'Request body: {"method":"eth_sendRawTransaction","params":["0x02f8788501823cf7e88202cd830f4248830f424882520894f1b4a63eaedce2283e33f2a6ed52005ea385ea15890670aeaf0994e6d34080c080a0bba113532b5b3c16796a8d329c8d708e2624d6596725d6a718ca721d83942266a05cd6b3f72fb70144e961b5a7936f4f6627f23edc66243fe1ec43ad0b2fa08a37"]}'
],
shortMessage: 'RPC Request failed.',
version: '[email protected]',
cause: { code: -32000, message: 'replacement transaction underpriced' },
code: -32000
},
code: -32000
}
}
Node.js v20.1.0
ELIFECYCLE Command failed with exit code 1.
Desktop (please complete the following information):
Currently Alto doesn't validate banned opcodes and banned storage accesses as per the EIP-4337 standard. In order to be fully compliant with the spec, we must add this validation.
The Eth-Infinitism bundler validation logic might be a good base to take as inspiration for this, however it is written using ethers.js while we are looking to use viem.
Describe the bug
So far, according to EIP-7562, arbitrary storage access is forbidden unless some requirements are met. One of this is that read-only storage in non-entity contracts is allowed only if the entity is staked:
If the entity (paymaster, factory) is staked, then it is also allowed:
...
- [STO-033] Read-only access to any storage in non-entity contract.
The issue (not sure if a bug or a feature) is that I've been playing with the idea of using ERC-1271 to set smart contract owners of smart contract wallets and I'm not sure whether the bundler allows for bypassing the storage validation rules.
Particularly, I've been using the ERC4337 module for Safe, which supports isValidSignature
through their checkSignatures
method that eventually checks the contract signature. This way I've been using a custom smart contract signer similar to the following:
contract P256Identity is IERC1271 {
using P256 for bytes32;
// bytes4(keccak256("isValidSignature(bytes,bytes)")
bytes4 internal constant EIP1271_MAGIC_VALUE = 0x20c13b0b;
struct PublicKey {
bytes32 qx;
bytes32 qy;
}
PublicKey publicKey;
constructor(bytes32 qx, bytes32 qy) {
publicKey = PublicKey(qx, qy);
}
function isValidSignature(
bytes32 digest,
bytes memory signature
) external view returns (bytes4) {
return
_verifyP256Identity(digest, signature)
? EIP1271_MAGIC_VALUE
: bytes4(0);
}
function _verifyP256Identity(
bytes32 digest,
bytes memory signature
) internal view returns (bool) {
PublicKey memory pubKey = publicKey();
(uint256 r, uint256 s) = _splitSignature(signature);
if (normalize) {
digest = sha256(abi.encode(digest));
}
return digest.verify(digest, r, s, pubKey.qx, pubKey.qy);
}
function _splitSignature(
bytes memory signature
) private pure returns (uint256 r, uint256 s) {
r = uint256(bytes32(signature[00:31]));
s = uint256(bytes32(signature[32:63]));
}
}
After testing it out a couple of times (here's a test tx in Arbitrum) it's been working fine so far but I can't reason why.
The rationale of the validation rules is that it shouldn't be possible to create spam UserOperations (i.e. that fail due to a storage change), although that's impossible for this tiny contract (because storage can't be changed) then it should be possible to craft an operation that does change storage between the validation and execution.
To Reproduce
Reproducing the transaction I shared is quite difficult as it requires a custom off-chain signer, but here's the execution diagram according to the test tx I'm sharing: https://sepolia.arbiscan.io/tx/0x924257e84890082d39daeb9c34bd5839a559b13be06a93cb7597f6a58f59c6d5
sequenceDiagram
EntryPoint ->> SafeWallet1.4.1: validateUserOp()
SafeWallet1.4.1 ->> P256Identity: isValidSignature()
P256Identity -->> P256Identity: sload public key slots
Note right of P256Identity: Aren't validation rules violated here?
P256Identity ->> SafeWallet1.4.1: success
SafeWallet1.4.1 ->> EntryPoint: success
Expected behavior
Not sure whether it should be possible in the first place.
Additional Context
Seems like the corresponding rules is enabled:
alto/src/rpc/validation/TracerResultParserV07.ts
Lines 615 to 616 in a848d11
NOTE: Lower priority
Currently we don't persist incoming UserOperations in a mempool, and send them straight to be included on-chain. This is not a big problem right now because there isn't enough volume on-chain to justify waiting for multiple UserOperations until submission for inclusion, but this is required to be compliant with the spec.
Let's create a persistent mempool using Redis with ioredis
Hi, very appreciate this great work.
However, I am trying to use alto on a local testnet and could not make estimateUserOperation
json-rpc work. The error message is: Cannot decode zero data ("0x") with ABI parameters.
.
I investigated the code of alto and found that alto seems to call callExecute
function in EntryPoint
address during simulateHandleOp
:
alto/packages/rpc/src/gasEstimation.ts
Lines 197 to 205 in adb3696
I don't think there is a function called callExecute
in the entrypoint. Then I notice there is a special parameter in the RPC parameters called finalParam
, which seems to instruct the RPC backend to replace the bytecode of entrypoint to a given ExecuteSimulator
code.
alto/packages/rpc/src/gasEstimation.ts
Lines 24 to 59 in adb3696
Is this contract bytecode replacement feature in the Ethereum JSON-RPC standard? Or are you using a customized Ethereum node to handle such RPCs? How can I make this work on a local testnet (e.g., anvil)?
Arbitrum calculates gas usage differently than other chains, as explained here. Currently we just hardcode a gas usage multiple, but we should precisely calculate the actual gas limit to use based on Arbitrum's formula.
Currently executor.ts
is very unclean and cluttered. It would be very useful to refactor it, make it more declarative/functional/self-contained.
Hello guys,
Is there any guide on how to deploy this bundler in custom EVM networks?
Currently, most wallets call eth_gasPrice
to get a reasonable value for gas price for their user operations. However, in order for the bundler to be profitable, it needs to bundle User Operations with a gas price that is higher than the gas price of the transaction bundling the ops.
Instead of asking wallets to guess a good gas price to use, it would be better to expose an api that they can use to get a good value for what will be (statistically) acceptable for our bundler, including an overhead that gives the bundler some wiggle room in case gas prices increase.
This API endpoint could return a safeLow, standard, and fast gas prices, likely perhaps ranging from 10-20% on top of the normal gas prices the bundler would typically use (depending on network conditions and the speed chosen by the user: safeLow, standard, or fast).
Useful example from polygon/mumbai: https://wiki.polygon.technology/docs/tools/faucets/polygon-gas-station/
Might be good to standardize this endpoint over time and move it into the eth_
namespace so it's not pimlico specific.
Describe the bug
Detailed steps to Run bundler with command line arguments
current instruction in readme guide doesn't mention about passing command line args (yargs)
it gives
./alto
this runs in to error:
> [email protected] start /Users/kanth/Documents/defi/account-abstraction/pimlico/alto
> node packages/cli/lib/alto.js run
✖ Missing required arguments: entryPoint, signerPrivateKeys, utilityPrivateKey, minBalance, rpcUrl
Suggestion
entryPoint
on geth node / hardhat nodeIt would be nice to have instructions:
We are running Alto in a container locally alongside geth node as an infra for our AA tests. After switching to newer versions of go-ethereum, we have noticed that Alto is failing to estimate the callGasLimit
properly.
Steps to reproduce the behavior:
eth_estimateUserOperationGas
calls result in callGasLimit estimated to 9000, which leads to OOG errors in userop execution.Alto estimates callGasLimit correctly, so that userop is executed successfuly.
balanceOverride = false
eth_call
overrides supportWe have found that a magic number 9000
is used when estimated callGasLimit
is lower than 9000. Given our geth and Alto settings, Alto uses viem
to call EntryPoint.simulateHandleOp
, which does NOT set gasPrice
, feePerGas
and priorityFeePerGas
to eth_call
call.
[email protected] introduced a change to block.basefee
logic in the vm in the eth_call
, eth_estimateGas
etc. contexts: if gasPrice
is not specified, then block.basefee
returns 0. This is very important for the Alto Bundler, because its gas estimation depends on EntryPoint.simulateHandleOp
, which uses block.basefee
.
Currently, without specifying gasPrice
when calling EntryPoint.simulateHandleOp
, it returns paid
equal to gasUsed * maxPriorityFeePerGas
(without adding block.baseFee
), which results in callGasLimit
calculated to ~-1500000
.
As specified in the go-ethereum PR, specifying gasPrice
in eth_call
is sufficient. Indeed, after changing
const errorResult = await entryPointContract.simulate
.simulateHandleOp(
[
userOperation,
"0x0000000000000000000000000000000000000000",
"0x"
],
{
account: this.utilityWallet
}
)
in packages/rpc/src/validation/validator.ts
to
const errorResult = await entryPointContract.simulate
.simulateHandleOp(
[
userOperation,
"0x0000000000000000000000000000000000000000",
"0x"
],
{
account: this.utilityWallet,
gasPrice: userOperation.maxFeePerGas,
feePerGas: userOperation.maxFeePerGas,
priorityFeePerGas: userOperation.maxPriorityFeePerGas,
}
)
Alto's gas limit estimation works as expected.
We hope you can incorporate this fix, as we use Alto heavily in our workflow.
Hello,
I would like to ask if it's advisable to run Alto in production using the unsafe mode, I'm asking because we have been trying to get Alto to work by sending mint operation using the lightAccount but the bundler seems to revert it only works in unsafe mode is there something I'm doing wrong?
e.g. callData
const callData = await lightAccount.encodeCallData({
to: deployments.TestToken,
data: encodeFunctionData({
abi: ERC20_ABI,
args: [
lightAccount.address,
new BigNumber(2).multipliedBy(10 ** 18).toFixed(0),
],
functionName: "mint",
}),
value: 0n,
});
Then we use a paymaster
the estimateUserOperationGas endpoint manages to estimate gas but when we send the user operation we get issues
the bundler errors out with a response
{"message":"Cannot read properties of undefined (reading 'slice')"}
Currently all the options use names like lokiHost, lokiUsername, lokiPassword, signerPrivateKeys, utilityPrivateKey, maxSigners, etc.
Would be much cleaner to namespace them using dot notation for the configs where it makes sense, so something like loki.host, loki.username, loki.password, signer.privateKeys, signer.utilityKey, signer.maxSigners, etc.
The current config options are mostly written under packages/cli/src
Using the @account-abstraction npm packages in packages/utils/src/validation.ts
means having to import all of ethers.js as well even though we only needed that package for one the packUserOp
function.
Let's remove that dependency, and reimplement packUserOp
natively using viem
Describe the bug
vatidation
validation
(to fix typo) [email protected] test:spec
./test/run-spec-tests.sh
====================================================================
====== alto-launcher.sh
====================================================================
./test/run-spec-tests.sh: line 35: /Users/fallenangel/pimlico/alto/spec-tests/alto-launcher.sh: No such file or directory
OS: macOS
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.