Rust Reading Group EVM Technical Insights
Rust Reading Group EVM Technical Insights
Rust Reading Group EVM Technical Insights
There are more additional fields but those are not used in EVM execution: OmnerHash,
ParentHash, State/Transaction/Receipt Root, Bloom, ExtraData,MixHash/Nonce
BlockEnv and TxEnv can be seen as const field in EVM execution.
Additional cfg can be found in CfgEnv that contains ChainId and SpecId.
Logs are a way to log a message that something happened while executing smart
contract. It allows smart contract devs to have a nice way to notify users/machine
for specific event.
Log contain:
● Contract Address (From Call Context)
● Topics: that are just a list of 256 bit items. Item number depends on if it is
LOG0…LOG4. Items are popped from stack.
● Data: Is read from Memory and can be in arbitrary size (of course you pay for
every bite of it :))
Gas
Every Opcode is priced in terms of Gas. Every memory extension, DB load or store
has some dynamic or base gas calculation.
Eip1559 is improvement that introduced BaseFee that is taken from FeeSpend and
burned (destroyed) rest of Fee is transferred to miner that created the block. And
where our GasPrice is calculated as BaseFee+PriorityFee.
There was a way to get refund on gas GasRefund to decrease use gas. It is used in
SSTORE and SELFDESTRUCT (Idea was okay but was misused and in future
probably going to be removed).
Traces
It is utility used for debugging and useful for profiling of contract execution. It
contains every step of execution and its opcode, used gas, memory, stack.
It can be tied with solidity output to get full view of what is happening.
Call Traces are for some use cases eve more needed, it represent what contracts
are called.
Inspector
-Implementation detail but for traces to be obtain there are need to have some
kind of hooks that will allows us to inspect internal state in runtime.
Forge (upcoming tool for solidity devs) are using something similar with Sputnik to
obtain traces and apply cheatcodes that help with debugging.
It mostly does hooking on Host part and on every step inside Interpreter.
Interpreter code exploration
Host
- Is starting point of execution. It creates and calls Interpreter(Machine).
- As we already said, transaction can do: CALL and CREATE to EVM. so we
have inner_call and inner_create functions for recursive calls from Interpreter.
- Additionally Host acts as binding between Interpreter and needed data from
outside of EVM (database, environment, SLOAD,SSTORE).
- It handles contract calls and call stack. It needs to have ability to revert
changes that happened inside one contract call. Including created Logs. Needs
to handle selfdestruct storage reset.
- Reverts happen on OutOfGas, StackOverflow and StackUnderflow errors.
- Chooses if precompile contracts needs to be called if 0x00..01 to 0x00..09
addresses are called
Host contains:
● Subroutine: call stack with changes of every call. (Next slide)
● Precompiles: list of native hashes and curves.(Little bit later)
● DB: fetching account info, code, and storage from database.
● Environments: Transaction and Block information.
● *Inspector: Implementation dependent part for hooking of evm execution, main
usage is tracing
Subroutine (State and reverts)
It contains:
● State: current state of accounts and storages.
● Logs: Called OpCodes LOG1-4 are stored here.
● Depth: limit call stack to 1024
● Changelog: List of changes that happened in current changeset (contract
call).
○ Checkpoint is created at every call and and it gets its own ID that is incremented over time. If
some of contracts failed it’s checkpoint with its ID gets reverted and every ID that is higher.
○ If contract executed correctly usually its changelog should be merged with parent changelog,
but we are just leaving it and in return just continue using current changelog without merging.
Host Trait
Precompile Name Address Type
Use u64 for gas calculations, in spec it is U256: Spending u256 gas is not
something that is going to happen, for comparison current eth Block limit is 30M
gas.
Memory calculation for u64, u256 does not make sense. There is no hard limit on
memory used, but for every 32bit you use you pay for gas that acts as soft limiter.
Usually memory is specified as offset+size and memory is paid as
`max(offset+size)` number
Ethereum uses big-endian encoding and all PUSH values are in bigendian format,
this can be slow on most machines that uses little endian and have support for
u64 items. So in EVM stack is basically U256 that is [u64;4] (list of four u64
numbers) and we always convert those things back and forth.
GasBlock optimization: For the list of Instructions that we know that needs to be
executed in the row and we know how much static gas needs to be used, we can
pre calculate this in same loop as we are finding jump destinations. In the loop we
are adding gas block on every potential JUMP/CALL instruction.
Evmone done something similar to gasblock but additionaly for memory usage,
where if they are sure that memory is going to grow by X factor they precalculate
that growth and save some time on that.
Q&A