On the blockchain I can inspect the code of a contract, and see the EVM opcodes. Is there a way to decompile this and convert it back to (Solidity) source code?
Compilation back to the original source code is impossible because all variable names, type names and even function names are removed. It might be technically possible to arrive at some source code that is similar to the original source code but that is very complicated, especially when the optimizer was used during compilation. I don't know of any tools that do more than converting bytecode to opcodes.
Since contracts can access their own code and thus (ab)use the code for storing data, it is not always clear whether some part of the code is actually used as code or only as mere data and whether it makes sense to try and decompile it. It is computationally undecidable whether some piece of the code is reachable or not.
Note that there is no dedicated area to store creation-time fixed data (like lookup tables, etc). Apart from the code of the contract, it would also be possible to store the data in storage, but that would be way more expensive, so putting such data in the code are is actually a common thing.
There is a project Porosity now https://github.com/comaeio/porosity It's also integrated into Quorum toolchain https://www.coindesk.com/first-ethereum-decompiler-launches-jp-morgan-quorum-integration/
it is impossible to return back to the solidity code. you could just decode the Bytecode into opcodes.
look at this exemple : https://etherscan.io/opcode-tool?a=0x9e1b57fc92eba6434251a8458811c32690f32c45
In general as other users commented it is not possible to get back the original source code in practice. In theory, however, both compiled and source applications should produce exactly the same output (i.e. have the same semantics) so it should be possible to get a program in source code representation that does exactly the same thing as the bytecode. People have mentioned other decompilers such as Porosity. There are also decompilers (to an intermediate representation) called Mythril, EthIR and Vandal. As a user in 2018, the most complete decompiler available is https://www.contract-library.com. It's not a stand alone tool, but can decompile most of the contracts that are currently on the Ethereum mainnet and other testnets.
And this is the contract Badr Bellaj suggested: https://contract-library.com/contracts/Ethereum/0x9e1b57fc92eba6434251a8458811c32690f32c45
As you can see, even some of the function names are inferred automatically, based on the knowledge it acquired when trying to understand past contracts. Overall, decompilers for Ethereum are currently not designed to have their output optimized for human consumption, however they are optimized for consumption by other machines (algorithms) that can find security vulnerabilities.
This isn't a full answer, but describes an approach for which one might write a decompiler to Solidity that may be better than many of the existing decompilers.
It draws on experience in a Python decompiler I developed and maintain.
chriseth's excellent accepted answer describes the general case, and assumes you want decompilation to work in all situations over all of the code.
But many times this might not be the situation. Here are some scenarios where you may be able to do better than in the general case:
Suppose the code I want to decompile is a variant of code for which I do have the source code available. (This is also mentioned in Neville Grech's answer.) Maybe the bytecode/ewasm was generated from an older or newer version of source I do have. Here, I can draw on the fact that many of the variable names and their types I already do know, it is just that there may be some slight differences in the code. Even if a variable "err" in the source code I have is changed to "error" in the lost source code that was used in compilation, as long as the types are the same, it's not so bad to use variable name "err" even though it was "error". It is likely to give a more helpful name than an arbitrarily-fabricated name.
solc prior to the yul-based optimization does stack optimization and some local optimization but not the more disruptive kinds of "global" optimization. Even with optimization it may be possible to pattern match sequences of operation to obtain larger structures like
require. In Python I use a J. Earley parser which is cool because it allows grammars to be given in an ambiguous way. That is, a sequence of opcodes might match a grammar, several different high level constructs. But that's okay, because that is in fact the nature of the game. In decompilation you should not expect to get something that is exactly the source (although that can happen). Instead you should get something that is equivalent.
If additionally you happen to know the solc version that was used in compilation and/or the optimization level, that can further help to narrrow patterns that could possibly be emitted and hence shorten the grammar and make it less ambiguous. If the solc version is before 0.5 or so range you know that yul optimization is not a thing.
I am sure in solc there is boilerplate code all over the place. For example at beginning of contracts. That code can be matched. There is boilerplate code that solc uses to see if index in a dynamic array is acceptable. If this pattern of code is unique, then we might be able to infer that there is a dynamic array in use. Simlarly code that issues a "new" might fall into pattern-matching recognition.
Note: why do I write that you match on opcodes and not instructions (i.e. opcode and operand pair)? This is because if you are doing pattern matching you want to abstract a little bit; using the opcode for the instruction does that. In those cases where operand information should be included, what is done in the Python decompiler is that the opcode changes to reflect this additional piece of abstraction. There is nothing that dictates that you have to match on existing EVM opcodes. You can make up new opcodes, insert opcodes that might indicate control structure boundary or change some opcode names to assist in pattern matching.
- Sometimes you are not interested in decompiling all of the code, just a piece of it.