"Rich transactions" via EVM bytecode execution from externally owned accounts

RE immutability: The code is preserved in the transaction payload, which can be read and analyzed like any other.

I’d recommend that if this were adopted, wallets flag transactions to the target address with a warning message; it’d also be worth writing up a spec for a new RPC endpoint that wallets can support which accepts, say, a list of operations, and leaves it to the wallet to compose the bytecode. That way, the wallet can provide the user with a useful list of operations instead of an opaque blob to sign.

No, SELFDESTRUCT requires special-casing either way; without storage you still need to make sure it doesn’t zero out the nonce.

True, but I don’t believe any of those are mature enough to say whether they’re an issue or not.

I’m onboard with the idea of prohibiting SELFDESTRUCT, though I’d like to hear from implementers which option is simplest. I still don’t think there’s a compelling reason to prohibit storage access.

I really don’t think “we can’t think of a use-case right now” is a good reason to create a new special-case to prohibit something; we should prohibit something only if it’s reasonably likely to introduce complication to clients, or have security implications.

In that event, should we specify instead that the to field should be a single byte, similar to contract creation, rather than a 20-byte address?

Unlike that, this executes in the context of the EOA, which allows it to make use of resources owned by the EOA. A contract-creation TX is unable to operate on resources owned by its creator.

Can’t people just write a Solidity contract with some inline assembly that implements an interpreter for the bytecodes sent as msg.data?

It’s also safer, as stored data on EOA probably not be shared between services.

Someone could do that, but it would have a minimum of several hundred percent overhead, and wouldn’t solve the main problem this EIP solves - namely, being able to execute multiple operations as an EOA in a single transaction.

Can’t people just write a Solidity contract with some inline assembly that implements an interpreter for the bytecodes sent as msg.data?
It’s also safer, as stored data on EOA probably not be shared between services.

Yea, the big thing (and change) about the proposal is being able to execute code as the EOA. That is huge.

I’m generally very supportive of this concept, though I won’t have time to study it much until May. I would like to see the Rational cover the relevant reasoning I see here.

@Arachnid Could the payload of the transaction create a new contract at the EOA address? If that was possible a person could manage his account with a contract, which I think combined with the diamond standard, would be amazing.

There is not currently any way to deploy code to a chosen specific address, so this would not be possible without the introduction of some new opcode or other technique. Such functionality isn’t included in this EIP.

What is the status of this? Will you be creating an EIP @Arachnid? Any chance of this landing in Berlin?

In general, I do like the idea to pack more transactions together. However, how this is proposed seems like a pretty big step for me, especially due to UX and security implications.

An user would currently, before signing a transaction, know the exact amount of Ether they will maximally spend. This is thus callValue + gasPrice*gas. However, an user can now want to pack multiple transactions together via a non-malicious looking “rich transaction”. The easiest is of course a SELFDESTRUCT opcode. But what about a “multisend” transaction where an user sends Ether to multiple addresses? They could read some storage field from somewhere via CALL returning the addresses and amount to send to. One could frontrun this transaction to update the storage.

Especially in terms of Ether, to prevent that users lose all their funds, I suggest to disable the possibility to SELFDESTRUCT an account (think of a possibility where the rich transaction DELEGATECALLs to another address and this address now runs into a SELFDESTRUCT opcode (even this cannot be verified beforehand (that one cannot run into SELFDESTRUCT) - if it’s created via CREATE2 then the code can be changed via a frontrun).

I also suggest that the VALUE field of the transaction is, instead of being transfered to the precompile address (the EIP says that it behaves like DELEGATECALL - but you cannot send value there) this VALUE is the maximum amount what is spent. This needs some more thoughts though, but at this point I would suggest this is placed in a temporary account which is the intermediate account: any calls with transfer would then be deposited in this intermediate address (and also subtracted from there). At the end of the transaction the remaining balance of this address would be transferred to the EOA. What remains is the question what a BALANCE opcode checking the EOA balance would then return, and how to figure out what this intermediate accounts’ balance is.

I think the main point that users don’t know what they are maximally going to spend is extremely dangerous and should be prevented. If it gets implemented in this way, I think that any providers like MetaMask are going to put up big warnings that signing this transaction could possibly clear out the entire account.

Users should never be calling random untrusted code. If they are, there are a million ways to rob a person and we cannot protect people from all of them, so I don’t think we should be crippling useful features in an attempt to protect a user from themselves.

That being said, I’m not opposed to re-interpreting the value field for these transactions as require(balanceAfter >= balanceBefore - msg.value - gas * gasPrice). However, I wouldn’t want this EIP to get held up on that. It is a neat feature if it is nearly free to implement, but if it adds sufficient complexity I don’t think it is worth it.

I agree that users should not randomly sign any untrusted transactions. However, by now we have protection against some attacks a priori: think for example for Ether in general, it is not possible that another contract “steals” Ether: taking MetaMask as example it is clear how much Ether the user will spend on the transaction (which is the maximum amount it will consume).

This EIP does have no security mechanism to ensure that the user does not empty their account. I foresee that if this EIP gets implemented, providers like MetaMask are going to put up warnings and what will probably happen is that users will first create a new account, send the Ether + gas they want to spend there and THEN send a transaction, as these all-transaction-at-once schemes do have no protection whatsoever to prevent unintended side-effects.

Wallets like MetaMask should be updated to show the user the series of transactions that are being made. This series of transactions can show how much value is being attached to each.

This is similar to how MutaMask renders token transfers, which is there to protect the user from basically the same thing (contract call that sends value away from the user).

I don’t think users should ever sign a transaction like this without it being rendered. ETH is just one of many assets that can be sent away, and it is up to the signing software to help the user make an informed decision.

You have to be completely sure here that the attached value is static: if this is being read from an external location you cannot trust it. It thus has to be PUSHed on the stack and not being “calculated” in the transaction, i.e. dependent on external factors.

A possibility is of course that you can formalize these kind of transactions with a new type of EIP where the format is decided upon such that MetaMask can render these transactions.

I’m happy to submit my PR to make it an official draft; due to becoming a new father I don’t have a lot of spare time to push it forward myself, though. If anyone wants to become a co-author, I’d be grateful.

This is more or less what I’d recommend; metamask et al should refuse to allow dapps to send transactions to the precompile address directly, and instead expose an API that allows batching of multiple ordinary transactions, composing the bytecode themselves.

I see. It is very likely that that happens and more EIPs get built on top of this one. If we are all aware of the security implications I am fine with it.

A more detailed question then: how will CREATE work? Will it increase the EOA’s nonce?

Also pretty nitpicky: I don’t think rich transaction is a good term for this, it sounds like - well - a rich transaction i.e. someone sending a lot of value. Maybe something like series or multiple transactions?

It is using a different version of the word rich:

rich: interesting because full of diversity or complexity.

As much as I’m certain I’ll regret this, I can help out. If you get the initial EIP created as a draft, I can try to handle updates, questions, comments, feedback, etc. I’m not in a position to get a client implementation done though, sadly.

As a note we took this idea into https://ethresear.ch/t/eth1x64-variant-1-apostille/7365, but extended it to have its own transaction type and not messing with precompiles.

I have created a DRAFT EIP for this at EIP-2803

I made two changes from the original proposed by @Arachnid:

  1. I added an assertion that a transaction that transfers value is invalid. Nick’s version implied that any attached value would be burned, which feels unnecessarily punishing when we can just flag such transactions as invalid. I could be convinced by client devs that this will add enough complexity to make it not worth flagging the transaction as invalid, but I would like to hear such arguments before making that assumption.
  2. I added clarification that the CALLER will be set to the EOA when the CALL opcode is used. I think this was implied in the specification but not clear enough IMO.

@Arachnid I put myself as author because I didn’t feel it was appropriate to force you as author. However, if you want to be the sole author or a co-author let me know and I will happily add your name to the authors list.

Can someone more knowledgeable than I with how CREATE works recommend an answer to this? I can update the draft with whatever is reasonable.

I suspect we will need to bump the nonce because two CREATE operations in the same transaction need to result in different addresses, which I believe will require bumping the nonce. The alternative would be to constrain to a single CREATE call per transaction, but that seems like a worse solution to the problem all around. The one caveat here is that existing signing tools will miscalculate nonces when signing multiple rich transactions in a row, and figuring out how much the nonce increases will kind of suck for those tools.