Page Not Found
We could not find what you were looking for.
Please contact the owner of the site that linked you to the original URL and let them know their link is broken.
diff --git a/404.html b/404.html index 8dcf2cc..2f59e2d 100644 --- a/404.html +++ b/404.html @@ -3,10 +3,10 @@
-We could not find what you were looking for.
Please contact the owner of the site that linked you to the original URL and let them know their link is broken.
Your Docusaurus site did not load properly.
\nA very common reason is a wrong site baseUrl configuration.
\nCurrent configured baseUrl = ${e} ${"/"===e?" (default value)":""}
\nWe suggest trying baseUrl =
\nYour Docusaurus site did not load properly.
\nA very common reason is a wrong site baseUrl configuration.
\nCurrent configured baseUrl = ${e} ${"/"===e?" (default value)":""}
\nWe suggest trying baseUrl =
\nArchive
With the advancement of multichain development, CREATE3 emerges as a transformative and cheap alternative to the deployment of smart contracts. With the existing Solidity opcodes such as create
and create2
, the Create3 is built upon the solidity of create2
to grant developers control over contract deployment. By enabling developers to predetermine contract addresses before deployment, CREATE3 imbues them with a newfound sense of agency. This new approach not only enhances control but also fosters consistency and uniformity across Ethereum Virtual Machine (EVM) blockchains, allowing for the synchronization of smart contract ecosystems. Whether navigating the Ethereum mainnet, testnets, or private networks, CREATE3 facilitates the harmonious deployment of contracts with identical addresses, thereby heralding a new era of streamlined blockchain development.
For a more technical perspective, let's dive into Solmate's CREATE3 library, which is designed for deploying smart contracts to deterministic addresses without necessitating an initcode factor. The CREATE3 deploy
method operates by leveraging create2
initially to deploy a CREATE factory, with its nonce set to 1 which then is used to deploy the desired derived contract.
Another important part is the Proxy Bytecode.
bytes internal constant PROXY_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";
The Deployer.sol
contract is essentially a factory contract that deploys the contract bytecode to a deterministic address. The contract has a deploy
method that takes in the salt value and can take the bytecode of the contract to be deployed. In a more generic sense, you can pass any bytecode in so the deployer()
method can be,
function deploy(bytes memory _salt, bytes memory _bytecode, /* more args for constructor */) external {
bytes memory bc = abi.encodePacked(
_bytecode,
abi.encode(msg.sender)
// args,
);
CREATE3.deploy(keccak256(_salt), bc, _value);
}
For this contract, we've encapsulated the deployment of a contract identified as Ownerable.sol
. Utilizing the built-in methods, we extract the bytecode of Ownerable
and subsequently invoke the create3.deploy
function from the Solmate library. This invocation passes along the salt, the extracted bytecode, and any specified ether value as parameters. The create3.deploy
function is then responsible for deploying the contract using the provided bytecode.
It is important to note the given example using CREATE3 on multichain with the same address, you MUST use a wallet with the same nonce on each network. The best solution is to create an entirely new wallet and send some funds on each chain.
For our demonstration, we'll deploy contracts on the BASE Sepolia and Polygon Mumbai testnet. Begin by compiling and deploying Deployer.sol
. This action will advance the nonce of your wallet by one once the transaction is completed. Following this, switch your Metamask to the Mumbai testnet and deploy using the same newly created wallet. The outcome should be consistent across both networks. It's important to note that the Deployer.sol
used in both instances is identical, ensuring uniformity in the deployment process.
The examples of deployers on both networks are as follows,
@@ -69,8 +69,8 @@Before we go into the details of the EntryPoint, we need to understand the basic flow of how the EntryPoint works. The EntryPoint is a smart contract that is used to execute UserOperations
. The UserOperation
is a struct that contains all the necessary information to execute a transaction. The EntryPoint is used to execute multiple UserOperations at once. This is done to save gas costs and to make the execution of transactions more efficient.
The primary data structure for user interaction within the Account Abstraction framework is encapsulated in interfaces/UserOperation.sol
. This structure is typically created by the Bundler and transmitted to the EntryPoint contract. The UserOperation
struct comprises the following fields:
struct UserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
uint256 callGasLimit;
uint256 verificationGasLimit;
uint256 preVerificationGas;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes paymasterAndData;
bytes signature;
}
An example of a UserOperation
is provided below:
const emptyUserOp: UserOperation = {
sender: AddressZero,
callData: '0x',
nonce: 0,
preVerificationGas: 0,
verificationGasLimit: 100000,
callGasLimit: 0,
maxFeePerGas: 0,
maxPriorityFeePerGas: 0,
signature: '0x'
}
In contrast to the traditional approach of sending signed transactions to a mempool for validation, the initial step in ERC-4337 involves dispatching an operation in the form of a UserOperation. These operations are then forwarded to an alternative mempool. Users have the capability to dispatch multiple UserOperations concurrently through a Bundler smart contract, referred to as Bundler Transactions.
-The EntryPoint (EP) contract on Ethereum, found at 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789, is crucial for handling bundlerTransactions
.
You can explore this contract using Solidity's IDE ${SOLIDE_URL}/1/0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789)
As of writing this, its contract version is 0.6.0
and serves as the main hub for processing batches of UserOperations
. The contract offers two main methods: handleOps
and handleAggregatedOps
. We'll focus on handleOps
for now, leaving handleAggregatedOps
for later discussion.
The main flow of using the EntryPoint typically comes from the Bundler
contracts which are called the handleOps()
function handleOps(UserOperation[] calldata ops, address payable beneficiary) public nonReentrant
Before executing the UserOperations, the EntryPoint will validate each UserOperation. It'll create a UserOpInfo
array to store the information of each UserOperation.
for (uint256 i = 0; i < opslen; i++) {
UserOpInfo memory opInfo = opInfos[i];
(uint256 validationData, uint256 pmValidationData) = _validatePrepayment(i, ops[i], opInfo);
_validateAccountAndPaymasterValidationData(i, validationData, pmValidationData, address(0));
}
In order to populate the opInfos from the Bundler, the function will undergo validation check in its loops. In order to save gas it'll be in the uncheck
Go to implementation information for _validatePrepayment
+Go to implementation information for _validatePrepayment
After the validation of both the Account and Paymaster, the validation is checked to see if they expire in _validateAccountAndPaymasterValidationData
. If the validation is successful, the EntryPoint will execute the UserOperations.
Go to implementation information for _validateAccountAndPaymasterValidationData
+Go to implementation information for _validateAccountAndPaymasterValidationData
uint256 collected = 0;
emit BeforeExecution();
for (uint256 i = 0; i < opslen; i++) {
collected += _executeUserOp(i, ops[i], opInfos[i]);
}
_compensate(beneficiary, collected);
With all validation complete it'll emit an event before execution begins. Then start iterating through each user operation, executing them and adding the gas fees consumed by each operation to the total collected amount. After all operations are executed, it compensates the specified beneficiary with the total collected gas fees, transferring them to the beneficiary's address.
-Go to implementation information for _executeUserOp
-Go to implementation information for _compensate
-Go to implementation information for _executeUserOp
+Go to implementation information for _compensate
+function _validatePrepayment(uint256 opIndex, UserOperation calldata userOp, UserOpInfo memory outOpInfo)
private returns (uint256 validationData, uint256 paymasterValidationData)
The _validatePrepayment
function serves a pivotal role in upholding the integrity and safety of UserOperations within the account abstraction framework.
It takes in three parameters:
@@ -134,10 +134,10 @@This line of code ensures that certain numeric values within the UserOperation data, such as gas limits, do not exceed the maximum value representable by a 120-bit unsigned integer. This safeguard is implemented to mitigate the risk of overflow during subsequent calculations.
uint256 gasUsedByValidateAccountPrepayment;
(uint256 requiredPreFund) = _getRequiredPrefund(mUserOp);
(gasUsedByValidateAccountPrepayment, validationData) = _validateAccountPrepayment(opIndex, userOp, outOpInfo, requiredPreFund);
The function continues by calculating the gas needed to pre-fund the operation. This calculation is based on the UserOperation data and specific conditions defined within the _getRequiredPrefund
function. Furthermore, the function conducts validation checks using _validateAccountPrepayment
. These checks ensure that the account (Smart Contract Wallet), possesses adequate funds and allowances to cover the operation's gas costs.
Go to implementation information for _validateAccountPrepayment
+Go to implementation information for _validateAccountPrepayment
if (mUserOp.paymaster != address(0)) {
(context, paymasterValidationData) = _validatePaymasterPrepayment(opIndex, userOp, outOpInfo, requiredPreFund, gasUsedByValidateAccountPrepayment);
}
This next stage is optional, contingent upon the bundler's inclusion of a paymaster for the UserOperation. The paymaster is an integral component in Account Abstraction as it enables users to settle transaction fees such as utilizing ERC-20 tokens rather than native tokens like ETH. Acting as an intermediary, the Paymaster gathers ERC-20 tokens from users and remits ETH to the blockchain for transaction facilitation. Therefore, this aspect is a crucial addition to the EntryPoint, permitting the Bundler to cover the UserOperation costs using ERC-20 tokens instead of native tokens such as ETH.
-Go to implementation information for _validatePaymasterPrepayment
+Go to implementation information for _validatePaymasterPrepayment
uint256 gasUsed = preGas - gasleft();
if (userOp.verificationGasLimit < gasUsed) {
revert FailedOp(opIndex, "AA40 over verificationGasLimit");
}
outOpInfo.prefund = requiredPreFund;
outOpInfo.contextOffset = getOffsetOfMemoryBytes(context);
outOpInfo.preOpGas = preGas - gasleft() + userOp.preVerificationGas;
After completing the necessary gas calculations and validations, the function ensures that the gas utilized during validation does not surpass the specified verification gas limit. Upon success, it finalizes the pre-funding details within the outOpInfo
structure, encompassing the pre-fund amount, memory context offset, and pre-operation gas usage.
outOpInfo.preOpGas
is determined as the sum of the total gas used thus far and the userOp.preVerificationGas
.In summary, _validatePrepayment
assumes the role of guaranteeing the validity and safety of UserOperations within the account abstraction framework. It encompasses crucial tasks such as gas tracking, hashing, validation, and pre-funding calculations, ensuring the seamless and secure execution of operations.
Go back to handleOps
-Go back to handleOps
+_createSenderIfNeeded(opIndex, opInfo, op.initCode);
This internal method is crucial for validating the operation with a Smart Contract Wallet (SCW). Initially, it calls upon a Factory contract to create the account if required, utilizing _createSenderIfNeeded
. The Wallet contract generated by this factory must adhere to interfaces/IAccount.sol
, which includes the validateUserOp
function. This function is essential for validating the UserOp's signature, enabling the EntryPoint to execute operations on a Wallet account.
Once the Smart Contract Wallet is deemed valid for further validation, the method proceeds to perform calculations on the gas funds and validate the validateUserOp
function on the SCW if and only if paymaster == address(0)
. This condition signifies that the SCW, either passed or generated, will be responsible for covering the current UserOperation execution(s).
handleOps.validatePrePayme
try IAccount(sender).validateUserOp{gas : mUserOp.verificationGasLimit}(op, opInfo.userOpHash, missingAccountFunds)
There is also the introduction of reverting the entire transaction if validations fail from the SCW or the call runs out of gas. Mainly the FailedOp
will revert the transaction.
Upon successful validation, both gasUsedByValidateAccountPrepayment
and validationData
provided by the SCW through its IAccount
interface are captured. It is crucial that the validation logic is tailored and executed according to each user's preferences and requirements.
-Go back to _validatePrepayment
-_validatePaymasterPrepayment
+Go back to _validatePrepayment
+_validatePaymasterPrepayment
uint256 preGas = gasleft();
MemoryUserOp memory mUserOp = opInfo.mUserOp;
address paymaster = mUserOp.paymaster;
DepositInfo storage paymasterInfo = deposits[paymaster];
uint256 deposit = paymasterInfo.deposit;
if (deposit < requiredPreFund) {
revert FailedOp(opIndex, "AA31 paymaster deposit too low");
}
Similarly to the _validateAccountPrepayment
, this time, it'll check the paymaster's deposit balance in EP. If there is enough despot compared to the provided, it'll deduct the that the the requiredPreFund from the Paymaster's deposit,
MemoryUserOp memory mUserOp = opInfo.mUserOp;
uint256 verificationGasLimit = mUserOp.verificationGasLimit;
require(verificationGasLimit > gasUsedByValidateAccountPrepayment, "AA41 too little verificationGas");
uint256 gas = verificationGasLimit - gasUsedByValidateAccountPrepayment;
@@ -166,8 +166,8 @@ At this point, the EntryPoint call stack should handleOps.validatePrePayme
After deducting the deposit as mentioned call the validationOp's validatePaymasterUserOp
for paymaster of interfaces/IPaymaster.sol
and with the userOp.verificationGasLimit
as gas limit and return the validation Data.
IPaymaster(paymaster).validatePaymasterUserOp{gas: gas}(op, opInfo.userOpHash, requiredPreFund) returns (bytes memory _context, uint256 _validationData)
This will return context object and validationData
.
-Go back to _validatePrepayment
-✔️ validateAccountAndPaymasterValidationData
+Go back to _validatePrepayment
+✔️ validateAccountAndPaymasterValidationData
function _validateAccountAndPaymasterValidationData(uint256 opIndex, uint256 validationData, uint256 paymasterValidationData,
address expectedAggregator)
validateAccountAndPaymasterValidationData
serves to validate the validation data from both the Smart Contract Wallet (SCW) and Paymaster. It verifies if the validation data has expired and reverts the transaction if it has. Notably, in the Ethereum EntryPoint, the address(0)
represents the expectedAggregator
.
Before unpacking the validationData, An example of validation data is as follows as taken by account-abstraction/ethereum/contracts/TokenPaymaster.sol
. Note this is just an example, other Paymaster's or SCW validation will have different validation data depending on it implementation.
@@ -185,24 +185,24 @@ At this point, the EntryPoint call stack should handleOps.validatePrePayme
Furthermore, validation is conducted by comparing the current block timestamp with the validUntil and validAfter timestamps obtained from the validation data. The outOfTimeRange
variable is set based on whether the current timestamp exceeds the validUntil timestamp or falls before the validAfter timestamp. If outOfTimeRange
is true
, it indicates that the paymaster has expired or the operation is not yet due.
outOfTimeRange = block.timestamp > data.validUntil || block.timestamp < data.validAfter;
In summary, the code snippet checks the status of the aggregator and verifies the validity of the paymaster based on timestamps, ensuring that the operation is executed within the designated time range. If any discrepancy is detected, the function reverts the transaction with an appropriate error message, such as "signature error" or "paymaster expired or not due."
-Go back to handleOps
-numberMarker()
+Go back to handleOps
+numberMarker()
//place the NUMBER opcode in the code.
// this is used as a marker during simulation, as this OP is completely banned from the simulated code of the
// account and paymaster.
function numberMarker() internal view {
assembly {mstore(0, number())}
}
This function is mainly useful for the method simulateValidation
, for tracing and checkpoints throughout the code. For our purpose, we don't need to really worry about this.
-🔧 _executeUserOp
+🔧 _executeUserOp
function _executeUserOp(uint256 opIndex, UserOperation calldata userOp, UserOpInfo memory opInfo)
private returns (uint256 collected)
bytes memory context = getMemoryBytesFromOffset(opInfo.contextOffset);
The code begins by retrieving the context from memory. The context is stored as a byte array and contains essential information needed for the Paymaster.postOp
function.
try this.innerHandleOp(userOp.callData, opInfo, context) returns (
uint256 _actualGasCost
) {
collected = _actualGasCost;
}
The code then attempts to execute the UserOperation by invoking the innerHandleOp
function. This function, implemented by the Paymaster, takes userOp.callData
, opInfo
, and context
as parameters. Upon invocation, innerHandleOp
returns the actual gas cost incurred by the operation, which is subsequently added to the collected
variable.
-Go to implementation information for innerHandleOp
-innerHandleOp
+Go to implementation information for innerHandleOp
+innerHandleOp
function innerHandleOp(
bytes memory callData,
UserOpInfo memory opInfo,
bytes calldata context
) external returns (uint256 actualGasCost)
The innerHandleOp
method is invoked to execute the UserOperation
calldata on the wallet contract. Leveraging the Exec
solidity library, available in util/Exec.sol
, developers gain access to a range of utility functions designed for diverse contract calls. These include regular call, staticcall, and delegatecall functionalities, along with features for retrieving return data and reverting with explicit byte arrays. Such capabilities empower developers to interact flexibly and efficiently with other contracts directly within Solidity contracts, seamlessly managing value transfers and data retrieval. In the following code snippet, Exec.call
is utilized—a low-level call function—utilizing the calldata provided by userOp.callData
.
if (callData.length > 0) {
bool success = Exec.call(mUserOp.sender, 0, callData, callGasLimit);
if (!success) {
bytes memory result = Exec.getReturnData(REVERT_REASON_MAX_LEN);
if (result.length > 0) {
emit UserOperationRevertReason(opInfo.userOpHash, mUserOp.sender, mUserOp.nonce, result);
}
mode = IPaymaster.PostOpMode.opReverted;
}
}
unchecked {
uint256 actualGas = preGas - gasleft() + opInfo.preOpGas;
//note: opIndex is ignored (relevant only if mode==postOpReverted, which is only possible outside of innerHandleOp)
return _handlePostOp(0, mode, opInfo, context, actualGas);
}
-Go to implementation information for _handlePostOp
-_handlePostOp
+Go to implementation information for _handlePostOp
+_handlePostOp
function _handlePostOp(uint256 opIndex, IPaymaster.PostOpMode mode, UserOpInfo memory opInfo, bytes memory context,
uint256 actualGas) private returns (uint256 actualGasCost)
address refundAddress;
MemoryUserOp memory mUserOp = opInfo.mUserOp;
uint256 gasPrice = getUserOpGasPrice(mUserOp);
address paymaster = mUserOp.paymaster;
if (paymaster == address(0)) {
refundAddress = mUserOp.sender;
} else {
refundAddress = paymaster;
// ...
}
When a paymaster is specified and its validation results in a non-empty context, the surplus amount is reimbursed to the account or paymaster, depending on its involvement in the transaction request. As mentioned, the following code executes the IPaymaster
's postOp
function, which is another essential method similar to validatePaymasterUserOp
.
@@ -215,11 +215,11 @@ At this point, the EntryPoint call stack should handleOps.validatePrePayme
- The
_incrementDeposit
function in the StakeManager
contract is invoked to increase the paymaster's deposit by the actual gas cost.
- The
actualGasCost = actualGas * gasPrice
calculation determines the actual gas cost of the operation, which is stored as the value of collected
in the handleOps
function.
-Go back to handleOps
-💵 _compensate
+Go back to handleOps
+💵 _compensate
/**
* compensate the caller's beneficiary address with the collected fees of all UserOperations.
* @param beneficiary the address to receive the fees
* @param amount amount to transfer.
*/
function _compensate(address payable beneficiary, uint256 amount) internal {
require(beneficiary != address(0), "AA90 invalid beneficiary");
(bool success,) = beneficiary.call{value : amount}("");
require(success, "AA91 failed send to beneficiary");
}
The final step is to compensate the beneficiary with the collected fees. The collected fees are transferred to the beneficiary address. The beneficiary address can be any address where the bundler wants to receive the refund as provided in the handleOps
.
-Conclusion
+Conclusion
In conclusion, the EntryPoint efficiently executes the bundled UserOperations and ensures fair compensation for the beneficiary by collecting fees. This is the flow of handleOp
. There is also handleAggregatorOp
. Note also the EntryPoint extends StakeManger
found in core/StakeManger.sol
which as mentioned is responsible for managing deposits
and stakes to ensure reimbursement for beneficiaries during the execution of handleOps and handleAggregatedOps functions. Deposits represent balances used to cover the costs of UserOperations, while stakes are values locked for a specified duration by paymasters, crucial for the reputation system. To learn more about the EIP proposal and its specifications, you can refer to the official document here.
]]>
handleOps.validatePrePayme