Standalone
Self-Hosted EVM
Part V
Calls and Static Calls
With the basic instructions out of the way, we are left with more instructions that involve inter-transaction interaction. These calls are more complex because they initiate internal transactions.
Internal transactions
Internal transactions are calls initiated by a contract. There are two important considerations when implementing internal transactions:
Each new internal transaction has its own stack and memory. In other words, for each new call, a new execution scope should be initialized. This means that we need to invoke
Core._execute
again.The internal transaction might revert. If it does, the caller should safely handle the reversion. As a result, we cannot naively invoke
Core._execute
because if the function reverts, the caller will not be able to catch the reversion.
To account for these nuances, we can initiate internal transactions in the sEVM by:
Defining a new interface
ICrossTx
, that exposesCore._execute
to only the sEVM (seecontracts/interfaces/ICrossTx.sol
), and have oursEVM
contract implementICrossTx
.To initiate an internal transaction, call
Core._execute
throughICrossTx
withtry
/catch
. If this call reverts (i.e. the virtual internal transaction reverts), we will catch the revert and handle it appropriately.
Still confused? Perhaps an example will make more sense...
Call
Let us study our first complex instruction: CALL
. It is used to call another contract or EOA from within the EVM. CALL
reads and writes the following values from/to the stack:
The CALL
subroutine should never revert. If the invoked call reverts, the subroutine should safely handle the revert by pushing false
onto the stack and storing the revert message in scope.returndata
.
We will have CALL
recursively call an external function (ICrossTx.call
) and handle possible reverts by using try
/catch
. Part of the code has been written for you.
function opCall(Scope memory scope) internal {
...
try ICrossTx(address(this)).call(...) {
// SUCCESS ...
} catch (bytes memory revertData) {
// FAILURE ... }
}
}
ICrossTx.call
`We will need to implement `ICrossTx.call`, which must:
Validate and process the call's value
Create the appropriate `Context
Read the appropriate `Contract`
Call `_execute(Context, Contract, bool)` while preserving the `readOnly` flag.
Additionally, all basic revert conditions should be considered.
Error | Expected Message |
|
|
|
|
StaticCall
Static calls are similar to calls, except they are read-only. Since opStaticCall
will internally invoke ICrossTx.staticCall
, we will need to implement both opStaticCall
and ICrossTx.staticCall
.
Error | Expected Message |
|
|
|
|
Your Task
Implement CALL
by defining:
opCall
incontracts/libraries/Instructions.sol
call in
contracts/sEVM.sol
Then, implement STATICCALL
by defining:
opStaticCall
incontracts/libraries/Instructions.sol
staticCall in
contracts/sEVM.sol
Run tests in Questplay
Then, one day, the Great Archmage disappeared from the world. None knew where they had gone, but their disciples swore to continue their work, teaching all those who wished to learn…