EIP-7702: Set EOA account code for one transaction

One way to solve this issue (at the expense of increased complexity) is to enforce that the nonceManager address is associated with the EOA at the mempool level. If the nonceManager is a contract and its address is derived from the EOA address by perhaps using the EOA as a salt to CREATE2 (or similar), a proof for nonceManager address derivation which includes the EOA can be included with the transaction to prove that the nonceManager is associated with the EOA.

While this does increase complexity, I don’t think these details should be included in the EIP and can perhaps be included in ERC7562.

One way to solve this issue (at the expense of increased complexity) is to enforce that the nonceManager address is associated with the EOA at the mempool level

I’d argue we don’t want to add more things into the mempool.

Doesn’t this EIP also break an invariant for code not changing at an underlying address? If you have logic today that performs EXTCODEHASH, and store those results (in a mapping against address) you have the possibility of having an infinite number of results.

1 Like

Assuming that the mempool allows submitting transactions with multiple nonces, wouldn’t bumpNonce(k) allow invalidating all transactions between nonces c and c+k present in the mempool, where c is the current nonce of the EOA? Does this potentially qualify as a mass-invalidation attack vector?

Do I understand correct that in order to execute smart contract code from EOA via 4337, the TX type would set contract code and hence create(initCode) part of 4337 would not be needed to be called (code already exists)?

This EIP is to give some AA functionalities to EOAs.
Yes, EOA doesn’t have code, and doesn’t deploy it - the transaction is executed “as if” the code is there.
This is an interim solution, and by no means a replacement for a full-fledged AA solution.
From security perspective, you’re at a much better position if you deploy an ERC4337 account, rather than trying to use an EOA using this EIP-7702 transaction.

1 Like

Should we hold off including 3074 to chat about this more? Or are we thinking this will be mainly additive with 3074?

I strongly vote for 3074 to also be included, ourselves and many other embedded wallet providers simply want to remove friction with gas sponsorship & transaction without increasing costs.

For context, most / all embedded wallets already have a relatively high level of trust delegated from their customers or end users so 3074 works out perfectly. 3074 remains the perfect next step for all the wallets that rely on 2771 meta transactions to move to protocol-native solutions without the requirement to whitelist a forwarder, and for simpler solutions that don’t require an entire paymaster infra.

Costs are significantly higher for 7702 compared to 3074 once you add revocation, chain verification and other things which is the highest worry for the majority of the ecosystem.

I want to highlight that incentive are very tricky in the conversation because several people chiming in here are building businesses that charge fees on sponsored gas so higher gas fees are better business-wise. But for any application, protocol, or business that don’t make money off of sponsored gas: it’s a net negative.

Separately, 3074 is a lot simpler to support for many of us who may just want to maintain an invoker without much more involved work.

Finally, I do think that users should be able to choose on a per-transaction basis which path they prefer between 3074 & 7702 as the properties are very different in practice:
• 3074: much cheaper, simpler, must be more trusted
• 7702: more expensive, more complex, trust delegation scales with complexity

2 Likes

That’s quite interesting! If contract_code is deployed directly, then even if it checks chain id or nonce, an attacker could bundle any of your previous signatures to mess with token receipts for a particular bundle.

I think initcode would prevent this? The initcode could revert, making the deployment fail.

The root cause is not the initialization, so adding initcode won’t help here.
I only mentioned it as an issue that an EIP-7702-wallet should be aware of the fact, and not attempt to do anything special within this callback.

The only mitigation is have persistent code - that is, deploy a full-fledged AA contract at that address.

The issue I’m raising is a bit different.

Consider this (somewhat) simple example:

// XXX: Unsafe; example only.
contract NonceStore {
    mapping (address => uint256) public nonces;

    function takeNonce(uint256 expected) external {
        require(expected == nonces[msg.sender]++);
    }
}

contract Ephemeral {
    NonceStore constant public nonceStore = NonceStore(address(0x01));

    function approveAndCall(
        uint256 nonce,
        ERC20 token,
        uint256 wad,
        address spender,
        bytes memory data
    ) external {
        nonceStore.takeNonce(nonce);
        token.approve(spender, wad);
        (bool a,) = spender.call(data);
        require(a);
        token.approve(spender, 0);
    }
}

Assume that NonceStore is previously deployed at address 0x01, and I’m using Ephemeral in a 7702 transaction. Works great, and my approve-and-transfer goes through fine. It even has replay protection!

Now imagine some time has elapsed, and you want to send me an an ERC-1363 (or ERC-1155 or whatever) token from your own 7702-enabled account.

A malicious person could take my old Ephemeral signature and put it in front of your signature in the 7702 bundle. Obviously the nonce check will fail, but because my EOA now has code in it, the receive callbacks will fail, and the bundler can cause your transaction to fail.

The substituted user accounts should be started warm for the purposes of eip-2929.

1 Like

FYI: I believe some restriction should apply to protect the EOA and the mempool against nonce increasses

Is this not analogous to handing a signed (but unbroadcast) transaction to someone, and now that someone can also bump your nonce?

geth (and I assume other clients) protect against similar attacks by limiting the number of pending transactions per account.

Is using 7702+CREATE/CREATE2/etc. worse than invalidating pending transactions using a private mempool and a number of normal transactions?

That would be fine. Doesn’t matter if it’s the code or its hash, just as long as it’s not just an address. It also makes it more efficient since the hash doesn’t need to be in the transaction itself. It can be read on-chain and used for verifying the transaction. I’m in favor of doing that.

2 Likes

The contract would have to implement these functions. During a 7702 transaction the account is not an EOA, it’s a temporary contract. See “best practices” above.

I agree. That’s what @yaonam suggested and I think it’s the right trade off. I support replacing contract_code with an address as long as the signature covers the contract’s code (which doesn’t need to appear in the transaction since it’s on-chain). And then we still don’t need chainid because it ensures the code is the same on every chain.

@SamWilsn - this is in contrast to what I said before about the address, so I’m pointing it out here. Transaction becomes smaller (only an address), cross-chain functionality is preserved, and so is safety (due to the code hash verification). WDYT?

1 Like

Right. 7702 is not a replacement for account abstraction. If you need the account to have the same logic when others interact with it, use a smart account. 7702 is meant to bring some of the benefits to EOA, not every benefit. Still a good trade off for many users.

You’re proposing a way to make NonceManager “associated” with the EOA in the ERC-7562 sense, much like associated storage. That would work but it means that every EOA needs to actually deploy a contract to be used as its NonceManager. If you’re already deploying a contract, why not just deploy a smart account and get the full benefits of AA?

It would have to be known to the protocol, and in fact ERC-7562 doesn’t need to know about it. Revocation is a protocol feature, not a 4337-related feature. The protocol needs to stop respecting a signature if it has been revoked, which requires it to know about NonceManager. This does complicate the EIP and I’m still not sure it brings much benefits beyond maxNonce - which keeps things as simple as possible while supporting protocol-level revocation.

1 Like

Flags for what to sign over and whether to keep code at the end.

  1. If EIP7702 has already been implemented, the cost of adding a new algorithm isn’t high.

  2. The risk of permanently upgrading an EOA to a CA is significant (as EOAs carry historical baggage), but addresses generated by algorithms like secp256r1 are not EOAs, so permanently setting code carries no additional risk.

  3. People need a more stable way to ensure deterministic deployments.

For example:

  • 0x00: Temporary upgrade And secp256k1
  • 0x10: Permanent upgrade And secp256k1
  • 0x01: Temporary upgrade And secp256r1
  • 0x11: Permanent upgrade And secp256r1
    …

Or, if you think supporting secp256r1 isn’t necessary, just determine the contract address using the eoa+1 method.

The higher-level abstraction of upgrading an EOA to a CA is verifying an address and setting code for it. I’m sure someone will use your protocol to deploy contracts; otherwise, we should better support this operation at the protocol level.

Hm, that means you need to have the code you want to run already deployed on chain? A tiny bit less flexible I suppose, but I’m not strictly opposed.

From a quick estimate, the smallest reasonable proxy is what, like 50 bytes? So we’d be saving 30 bytes per transaction. We’re sure that’s worth it?