Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract factory deploy compatible with EIP-7702 #4961

Open
NicoSerranoP opened this issue Mar 13, 2025 · 0 comments
Open

Contract factory deploy compatible with EIP-7702 #4961

NicoSerranoP opened this issue Mar 13, 2025 · 0 comments
Labels
enhancement New feature or improvement.

Comments

@NicoSerranoP
Copy link

NicoSerranoP commented Mar 13, 2025

Describe the Feature

Problem

I love the simplicity of using etherjs's signer when interacting with contracts. We built our project libraries and functions relying on it. E.g:

deployPoll(signer: Signer, parameter1: string, ...)

We wanted to implement gasless smart accounts (EIP-7702) using ZeroDev infrastructure. In simple terms: instead of sending our transactions with eth_sendTransaction we will be sending it with eth_sendUserOperation to their bundler RPC.

The problem was that we had to change all our functions to work with this new library. So instead of:

const factory = new RandomContractFactory(signer);
const contract = await factory.deploy(5n);

we will be doing something like

const deployCallData = await kernelClient.account.encodeDeployCallData({
    abi,
    args,
    bytecode,
  });
const tx = await kernelClient.sendUserOperation({
      callData: deployCallData,
      sender: kernelClient.account.address,
 });

Zerodev tutorial to deploy contracts here.

This mean code duplication and that is bad news.



Current Solution

The Zerodev team created an "adapter" to transform the kernelClient (a smart account client) to a signer:

import { KernelEIP1193Provider } from '@zerodev/sdk/providers';
import { ethers } from 'ethers';
 
// Ensure to initialize your KernelClient here
const kernelClient = ;
 
// Initialize the KernelEIP1193Provider with your KernelClient
const kernelProvider = new KernelEIP1193Provider(kernelClient);
 
// Use the KernelProvider with ethers
const ethersProvider = new ethers.BrowserProvider(kernelProvider);
const signer = await ethersProvider.getSigner();

This provider maps every request made from the signer eth_sendTransaction to a eth_sendUserOperation. In other terms: it makes you feel you have a local signer of your smart account rather than a normal External Owned Account (EOA). You can check the provider code here.

Side idea: I think ether.js could do integrate this adapter natively so existing projects could use EIP-7702 without changing a lot of their codebase


Interacting with contracts (OK)

Everything works seamlessly when interacting with contracts. E.g:

const contract = RandomContractFactory.connect(contractAddress, signer);
const votes = contract.getVotes(); // RandomContractFactory.sol implements a getVotes() function that returns the votes
...

So no code changes right? Not so fast...


Deploying contracts with factory (FAIL)

I encountered a problem with the factory.deploy() function because ZeroDev delegates a call to a on-chain contract that executes the code and deploys the transaction itself. I modified the ZeroDev library to bypass this problem and it worked 🥳 . But then I noticed that the contract address set up in the contract was not right:

const factory = new RandomContractFactory(signer);
const contract = await factory.deploy(5n);
const contractAddress = await contract.getAddress();
// it would be an empty "Address" in etherscan and not a "Contract" address

This is caused because the factory code computes the address from the sender and nonce (it is assuming the contract is deployed by an EOA):

const address = getCreateAddress(sentTx);

In these cases, the contract is deployed by another contract so the computed address do not match the deployed contract address.



Solution in ethers.js

I created a small patch for our project that looks like this:

export const getDeployedContractAddress = (receipt: TransactionReceipt): string | undefined => {
  const [contractCreationEventTopic] = encodeEventTopics({
    abi: [
      {
        name: "ContractCreation",
        type: "event",
        inputs: [{ indexed: true, name: "newContract", type: "address" }],
      },
    ],
  });
  const addr = receipt.logs.find((log) => log.topics[0] === contractCreationEventTopic);
  const deployedAddress = addr ? `0x${addr.topics[1]?.slice(addressOffset)}` : undefined;
  return deployedAddress;
};


const factory = new RandomContractFactory(signer);
const deployedContract = await factory.deploy(5n);
const { hash } = deployedContract.deploymentTransaction()!;

const receipt = await signer.provider?.getTransactionReceipt(hash);
const address = getDeployedContractAddress(receipt! as unknown as TransactionReceipt);
contract = RandomContractFactory.connect(address!, signer);

I think we could implement this natively in ethers.js by adding a couple of extra things:

  1. Set the transaction type 4 or eip-7702 (my reference) in the overrides parameter of the factory.deploy function:

    async deploy(...args: ContractMethodArgs<A>): Promise<BaseContract & { deploymentTransaction(): ContractTransactionResponse } & Omit<I, keyof BaseContract>> {

  2. If the type is 4 or eip-7702 then wait for the transaction to happen and with the receipt execute the line:

const address = getDeployedContractAddress(receipt! as unknown as TransactionReceipt);

if not then keep the normal flow.

I am assuming that all delegate calls would emit an event similar to event ContractCreation(address indexed newContract);. If we want to do it more generic we could allow the developer to send the ABI Item that would return the deployed contract address in the overrides parameter.

Final thoughts

If you think it is a good idea I could open a PR and implement it. I noticed that you have a Discussions section but the only thing related to Account Abstraction or EIP-7702 I found was this issue (and this issue in the Issues section). I thought this solution was straight forward and I decided to open the issue to check for your comments.

Thank you for your time. This became a little more extensive that I thought.

Cheers,

Nico

Code Example

@NicoSerranoP NicoSerranoP added the enhancement New feature or improvement. label Mar 13, 2025
@NicoSerranoP NicoSerranoP changed the title Contract Factory deploy compatible with EIP-7702 Contract factory deploy compatible with EIP-7702 Mar 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or improvement.
Projects
None yet
Development

No branches or pull requests

1 participant