ERC 4337 : Account Abstraction for Ethereum Smart Contract Wallets

Posted By : Yogesh

Oct 01, 2024

Understanding Account Abstraction on Ethereum for Smart Contract Wallets

 

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

 

Why do we need Account Abstraction ?

 

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

 

Approaches to Implement Account Abstraction:


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


What is ERC 4337?

 

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.  


Implementation of ERC 4337:

 

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:


1. UserOperation Struct


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


2. EntryPoint 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

 

3. User Wallet Contract


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

 

4. Gas Payment Mechanism
 

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));
    }
}

 

5. Account Abstraction Wallet
 

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

 

A recent breakthrough: EIP-4337
 

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


Conclusion of Account Abstraction Using ERC 4337:
 

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. 

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

October 15, 2024 at 08:56 pm

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Telegram Button
Youtube Button
Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text