The Ethereum execution environment will provide a smart contract platform similar to Ethereum 1.0. From here on, it will just be referred to as Ethereum. In order to achieve the same guarantees as Ethereum 1.0, the execution environment must maintain some context that is immutable from the smart contract execution (i.e. concepts like msg.sender
). The approach taken by this proposal is to offer smart contracts a wrapped version of the mutating methods from the EEI, while validating that the code submitted to Ethereum does not make use of the unwrapped EEI functions. Using this framework, we also describe how msg.value
can be implemented to support value transfers of arbitrary token types.
Out of scope:
In general, the goal of this proposal is to present three novel concepts: key-value denominated context stored in the client, dynamically linked code, and arbitrary value transfers in transactions. The actual architecture of the final solution will likely be radically different than what we have outlined, but may include some of the novel concepts described here.
get_context() -> bytes
In some cases it may be valuable to store runtime context between calls that are managed by the client. In order to support this, we'll assume that the EEI methods which spawn new calls will take an additional ctx
parameter that will be pushed onto the context stack.
There are three special context keys, two of which cannot be set by a caller, that the client will need to manage:
es_caller: uint256
– The index of the execution script that invoked the current frame of execution. It should default to 2**256 - 1
if the current frame is the transactions entry point.es_callee: uint256
– The index of the execution script that running in the current frame of execution.ext: { string: bytes }
– A map of externally defined functions where the key is the function name and the value is the code to execute. Upon entering a new frame of execution via execute_code
or execute_script
, each function defined in this map will be dynamically linked (or appended to call code) into the new runtime environment.dynamically_link_function(name, code)
Stores a function in context
that it is dynamically linked in child runtimes.
Transaction
State
Value
To provide the security needed to execute arbitrary code such as smart contracts, the following functions will wrap the existing EEI functions.
call_contract(target, data)
The call_contract
method is used to execute arbitrary code submitted to the EES while maintaining the caller, callee, value structure. It operates similarly as call
would in Solidity.
For simplicity, the value
is sent to the target
before switching contexts. An additional WEI function receive_value
would probably be a better approach as it would allow contracts to reject unwanted tokens and other custom behaviors.
call_execution_script(es_id, data)
The call_execution_script
method allows contract writers to interact with other execution environments on Ethereum. It maintains the caller, callee, value relationship, although the execution script destination doesn't necessarily need to support Eth related operations. This opens the door for execution environment "ecosystems" that trust each other and can operate on the same shard without receipts.
set_storage
A contract should only have access to the storage value that is mapped to by its address. The set_storage
method leans on the ctx
described above to determine the current frame of execution and allows access to the Ethereum storage accordingly.
process_block
All execution will begin with process_block
. This will iterate through all transactions in the block, and will call the intended contract with the provided data. Contract initialization strategies are ommitted here in favor of clarity around the other novel concepts outlined above.
In Vitalik's latest phase 2 proposal he gives the following lifecycle of a contract call:
A contract call roughly works by starting with stack = operations[::-1], then while len(stack > 0), pop the top operation off the stack and run executeCode(target.code, (target.state, operation.calldata)); this would be expected to return (new_state, continuation); the state transition function would set the state to equal the new state, and then add the continuation to the stack if any.
Following that construction, it would be possible to build a context manager with no additional EEI functions using the existing tools available to beacon chain contracts. It might look something like the following pseudocode (msg.value
and receipts omitted for simplicity).
With this sort of execution environment in mind, the continuation paradigm would be a fundamental piece of all smart contract development. Although this could be abstracted away from the user through development tooling, the resulting binaries will still need to be a fractured set of functions which increases progressively as the number of contract calls rise.
This example below is provided for clarity. Cross shard calls are not addressed in this example, but would follow the same pre/post processing pattern in both paradigms.
There are some pros and cons to the continuation approach versus a context construction built into the client.
Pros:
Cons:
We believe that at the core Ethereum VM, it's best to not make assumptions about developers' intentions. Therefore, having a general purpose context manager outside the runtime will provide the same benefits as a continuation model plus better performance and clearer low level code.