Standalone

Self-Hosted EVM

close button

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:

  1. Defining a new interface ICrossTx, that exposes Core._execute to only the sEVM (see contracts/interfaces/ICrossTx.sol), and have our sEVM contract implement ICrossTx.

  2. To initiate an internal transaction, call Core._execute through ICrossTx with try/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:

selfhosted_EVM_07.webp

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 ... } } }
selfhosted_EVM_08.webp

ICrossTx.call

`We will need to implement `ICrossTx.call`, which must:

  1. Validate and process the call's value

  2. Create the appropriate `Context

  3. Read the appropriate `Contract`

  4. Call `_execute(Context, Contract, bool)` while preserving the `readOnly` flag.

Additionally, all basic revert conditions should be considered.

Error

Expected Message

INSUFFICIENT_BALANCE_ERROR

sEVM: insufficient balance

READ_ONLY_ERROR

sEVM: read only

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.

selfhosted_EVM_09.webp

Error

Expected Message

INSUFFICIENT_BALANCE_ERROR

sEVM: insufficient balance

READ_ONLY_ERROR

sEVM: read only

Your Task

Implement CALL by defining:

  • opCall in contracts/libraries/Instructions.sol

  • call in contracts/sEVM.sol

Then, implement STATICCALL by defining:

  • opStaticCall in contracts/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…