Posted By : Yogesh
A novel concept in blockchain, account abstraction aims to improve and harmonize user account functionality in decentralized systems. Contract wallets, also known as smart contract accounts, can replace traditional externally held accounts thanks to account abstraction and smart contract development. A contract wallet can be controlled by a single key, multiple keys, or even a complex system encoded into the contract itself. This opens up numerous possibilities and benefits for Ethereum and other blockchain networks. Account abstraction allows for more flexible and secure management of contract wallets compared to traditional externally held accounts. For more about blockchain, Ethereum, and smart contracts, visit our smart contract development services.
In the Ethereum network, two types of accounts currently exist:
Externally Owned Accounts (EOAs): controlled by private keys and typically of specific people or organizations.
Contract Accounts: smart contracts whose code is run according to predetermined logic.
Account abstraction seeks to unify the two types of Ethereum accounts:
This implies that smart contracts can now manage and carry out transactions on behalf of users rather than exclusively depending on private keys (as with EOAs), providing users with more flexibility and opening the door to new features like customizable security models, automated and gasless transactions, meta-transactions, and improved privacy. These developments streamline user interactions and increase the Ethereum ecosystem's potential.
Also, Read | How to Create an NFT Rental Marketplace using ERC 4907
The current configuration of the Ethereum network has several drawbacks:
Security Risks: Due to their binary structure, private keys can be lost or stolen, which can result in an irreversible loss of money.
User Experience: For new users who could find wallet security and gas principles confusing, EOAs demand private keys and gas costs in Ether, which causes friction.Hazards to Security: Due to their binary structure, private keys can be lost or stolen, which can result in an irreversible loss of money.
Limited Features: Advanced features like multi-signature wallets and daily transaction restrictions cannot be implemented on EOAs due to their lack of programmability.
By addressing these problems, account abstraction seeks to enhance the functionality, security, and usability of the network.
Also, Read | A Guide to Implementing NFT Royalties on ERC-721 & ERC-1155
Protocol-Level Changes
It entails modifying the Ethereum protocol to allow native wallets for smart contracts. Consensus is required for this strategy throughout the Ethereum network.
Layer 2 Solutions
Layer 2 networks provide the ability to offload transaction processing and implement unique transaction validation procedures.
ERC 4337 (Ethereum Request for Comments)
It suggests implementing account abstraction just at the application level, eliminating the need for protocol modifications.
Also, Read | How to Create and Deploy a Token Bound Account | ERC-6551
A new transaction handling mechanism called UserOperation objects is introduced in ERC 4337. By signing UserOperation objects, which bundlers aggregate and transmit to the network, users avoid submitting transactions straight to the Ethereum blockchain. Without relying on the current transaction flow, this method enables smart contract wallets to safely start transactions.
A number of essential elements are involved in the Solidity implementation of ERC 4337 (Account Abstraction), which combined allow for flexible and intuitive interactions with smart contracts. These are the primary elements to pay attention to:
Purpose: Represents a single user operation with all necessary information.
Key Fields:
sender: The address of the user or wallet executing the operation.
nonce: To prevent replay attacks and track the order of operations.
callData: The encoded data for the function call.
gasLimit: The maximum amount of gas that can be used for the operation.
maxFeePerGas & maxPriorityFeePerGas: Control over gas fees.
You may also like | How to Create an ERC 721C Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract UserOperationExample {
struct UserOperation {
address sender; // Address of the user sending the operation
uint256 nonce; // Unique nonce to prevent replay attacks
bytes callData; // Encoded data for the function call
uint256 gasLimit; // Maximum gas limit for the operation
uint256 maxFeePerGas; // Maximum fee per gas unit the user is willing to pay
uint256 maxPriorityFeePerGas; // Max priority fee per gas
}
// Example function to demonstrate the use of UserOperation
function exampleFunction(UserOperation calldata userOp) external {
// Validate the user operation (you would typically check nonce, gas limits, etc.)
require(userOp.sender != address(0), 'Invalid sender');
require(userOp.gasLimit > 0, 'Gas limit must be greater than zero');
// Here you would implement the logic to execute the operation
(bool success, ) = userOp.sender.call{gas: userOp.gasLimit}(userOp.callData);
require(success, 'Operation failed');
// You could also emit an event here for tracking purposes
}
}
Also, Discover | How to Create and Deploy an ERC404 token contract
Purpose: Central contract that receives user operations and executes them.
Key Functions:
executeUserOperation: Validates and executes the user operation, checking the sender's nonce, ensuring gas limits, and processing the call data.
Security Checks: Implement checks to prevent issues like underflow/overflow, invalid addresses, and ensure gas payment.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract EntryPoint {
event UserOperationExecuted(address indexed sender, bytes callData);
event UserOperationFailed(address indexed sender, bytes callData, string reason);
// This mapping tracks the nonce for each user to prevent replay attacks
mapping(address => uint256) public nonces;
function executeUserOperation(UserOperation calldata userOp) external {
// Validate the user operation
require(userOp.sender != address(0), 'Invalid sender');
require(userOp.nonce == nonces[userOp.sender], 'Invalid nonce');
require(userOp.gasLimit > 0, 'Gas limit must be greater than zero');
// Update the nonce
nonces[userOp.sender]++;
// Execute the operation
(bool success, bytes memory returnData) = userOp.sender.call{gas: userOp.gasLimit}(userOp.callData);
if (success) {
emit UserOperationExecuted(userOp.sender, userOp.callData);
} else {
emit UserOperationFailed(userOp.sender, userOp.callData, _getRevertMsg(returnData));
}
}
// Helper function to extract revert reason
function _getRevertMsg(bytes memory returnData) internal pure returns (string memory) {
if (returnData.length < 68) return 'Transaction reverted silently';
assembly {
returnData := add(returnData, 0x04)
}
return abi.decode(returnData, (string));
}
}
Also, Discover | ERC 3643 A Protocol for Real World Asset Tokenization
Purpose: Acts as the user's wallet to create and submit user operations.
Key Functions:
submitUserOperation: Collects user operation parameters and sends them to the Entry Point.
Nonce Management: Increments the nonce after a successful operation to prevent replay attacks.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import './EntryPoint.sol'; // Import the EntryPoint contract
contract UserWallet {
address public entryPoint; // Address of the EntryPoint contract
uint256 public nonce; // Nonce for tracking user operations
constructor(address _entryPoint) {
entryPoint = _entryPoint; // Set the EntryPoint contract address
}
// Function to submit a user operation
function submitUserOperation(
bytes calldata callData,
uint256 gasLimit,
uint256 maxFeePerGas,
uint256 maxPriorityFeePerGas
) external {
// Create the UserOperation struct
UserOperation memory userOp = UserOperation({
sender: address(this),
nonce: nonce,
callData: callData,
gasLimit: gasLimit,
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas
});
// Submit the user operation to the Entry Point
EntryPoint(entryPoint).executeUserOperation(userOp);
// Increment the nonce for the next operation
nonce++;
}
// Example function to demonstrate a callable function from the wallet
function exampleFunction(uint256 value) external {
// Implementation of the function logic
}
}
Also, Check | A Guide to Gasless ERC20 Token Transfer
Purpose: Determines how the gas for executing user operations is paid.
Considerations:
You might want to allow users to pay gas fees in tokens or implement a mechanism for sponsor payments (where another entity pays the gas).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
contract EntryPoint {
event UserOperationExecuted(address indexed sender, bytes callData);
event UserOperationFailed(address indexed sender, bytes callData, string reason);
mapping(address => uint256) public nonces;
// Function to execute user operation with gas payment
function executeUserOperation(
UserOperation calldata userOp,
address paymentToken,
uint256 paymentAmount
) external payable {
require(userOp.sender != address(0), 'Invalid sender');
require(userOp.nonce == nonces[userOp.sender], 'Invalid nonce');
require(userOp.gasLimit > 0, 'Gas limit must be greater than zero');
// Validate gas payment
if (paymentToken == address(0)) {
// Pay with Ether
require(msg.value >= paymentAmount, 'Insufficient Ether sent');
} else {
// Pay with ERC-20 token
require(IERC20(paymentToken).transferFrom(msg.sender, address(this), paymentAmount), 'Token transfer failed');
}
nonces[userOp.sender]++;
(bool success, bytes memory returnData) = userOp.sender.call{gas: userOp.gasLimit}(userOp.callData);
if (success) {
emit UserOperationExecuted(userOp.sender, userOp.callData);
} else {
emit UserOperationFailed(userOp.sender, userOp.callData, _getRevertMsg(returnData));
}
}
function _getRevertMsg(bytes memory returnData) internal pure returns (string memory) {
if (returnData.length < 68) return 'Transaction reverted silently';
assembly {
returnData := add(returnData, 0x04)
}
return abi.decode(returnData, (string));
}
}
Purpose:
To manage user actions, an Entry Point contract communicates with the Abstracted Account Wallet, which functions as a user-defined wallet. By offering a means of verifying and carrying out these procedures, it guarantees that activities may only be carried out by authorized users.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import './library/UserOperation.sol';
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
contract AbstractedAccountWallet {
using ECDSA for bytes32;
uint256 public constant SIG_VALIDATION_FAILED = 1;
uint256 public constant NONCE_VALIDATION_FAILED = 2;
uint256 public constant VALIDATION_SUCCESS = 0;
address public owner;
uint256 public nonce;
address public entryPoint;
// Events for logging important actions
event ExecutedOperation(address indexed sender, uint256 value, bytes data);
constructor(address _entryPoint) {
owner = msg.sender;
nonce = 0;
entryPoint = _entryPoint;
}
// Modifier to check if the caller is the owner of the contract
modifier onlyOwner() {
require(msg.sender == owner, 'You are not the owner');
_;
}
modifier onlyEntryPoint() {
require(
msg.sender == entryPoint,
'Only EntryPoint can call this function'
);
_;
}
// Function to validate a user-defined operation
function validateOp(
UserOperation calldata op,
uint256 requiredPayment
) public returns (uint256) {
// Send requiredPayment to EntryPoint
if (requiredPayment != 0) {
payable(entryPoint).transfer(requiredPayment);
}
// Check nonce
require(op.nonce == nonce++, 'Invalid nonce');
// Check signature
if (
owner !=
getHash(op).toEthSignedMessageHash().recover(
// op.signature[32:]
op.signature
)
) {
return SIG_VALIDATION_FAILED;
} else {
// return uint256(bytes32(op.signature[0:32]));
return VALIDATION_SUCCESS;
}
}
function getHash(
UserOperation memory userOp
) public view returns (bytes32) {
return
keccak256(
abi.encode(
bytes32(block.chainid),
userOp.sender,
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
userOp.maxFeePerGas,
userOp.maxPriorityFeePerGas,
keccak256(userOp.paymasterAndData),
entryPoint
// uint256(bytes32(userOp.signature[0:32]))
)
);
}
}
You may also like | How to Create an ERC 721 NFT Token
Since the account abstraction effort moved to a different strategy, which was unveiled in EIP-4337 in late 2021, both EIP-2938 and EIP-3074 are presently dormant. Building on the idea of a smart contract wallet is the goal of the new strategy.
However, remember that we already mentioned that the lack of proper infrastructure makes smart contract wallets challenging to use? Nevertheless, EIP-4337 seeks to address that without altering the L1 protocol in the process.
The proposal introduces a higher-level mempool that operates with a new object called UserOperations. Instead of traditional transactions, users will send UserOperations to this mempool. Validators then select these UserOperations, bundle them into a transaction, and submit them to a specialized smart contract called the EntryPoint contract. This contract manages transaction execution and validator rewards.
The method outlined in EIP-4337 simplifies the process for developers to create custom smart contract wallets.
Also, Know | Create a Simple Dividend ERC20 token
Account abstraction and ERC 4337 are two progressive approaches to Ethereum's development. This strategy is well-positioned to promote the wider use of blockchain technology and decentralised apps by giving priority to user experience, flexibility, and security, so making them more accessible and useful for regular users. The ideas and applications resulting from ERC 4337 will probably influence the direction of decentralised finance in the future and beyond as the ecosystem develops. In case you are looking to build your project using emerging ERC standards, connect without our skilled Solidity developers to get started.
November 21, 2024 at 11:22 am
Your comment is awaiting moderation.