Lesson 15
Lesson 15
Lesson 15
Proof of Cat
See post
Chaining casts
Example
Strings.toHexString(uint256(uint160(_address)), 20)
EVM Storage
A tool to show you storage at any block height
See article
Testing with forks
Forking
Forge supports testing in a forked environment with two different approaches:
Forking Mode — use a single fork for all your tests via the forge test --
fork-url flag.
vm.selectFork(optimismFork);
assertEq(vm.activeFork(), optimismFork);
}
assertEq(block.number, 1_337_000);
}
}
Airdrops using merkle trees
Merkle claimed Airdrop
See gist
See workshop from Open Zeppelin
Slides
Upcoming changes to Solidity
What would Solidity 1.0 and 2.0 look like ? Video
1.0
Modifiers jump rather than in lining
Try catch for custom errors
Immutable reference types
Generics
Algebraic data types (composite types such as tuples)
Operators for user defined types
Standard Library
Motivation
allow more pre compilation
make the language extensible
stop wasting memory
2.0
Compiler changes - rewrite the compiler in Rust
Allow components in Rust
Have Rust bindings
Language changes
- Separate implementation storage
- Improve clarity about state access / modification
- Maybe remove inheritance
- More control over storage layout
See docs
More talks from Devcon VI
Technical Details of the Solidity Compiler Video
Underhanded Solidity Video
Unlimited Size contracts Video
What's next in EVM ? Video
Symbolic computation Video
Verkle trees
https://vitalik.ca/general/2021/06/18/verkle.html
See article
and Ethereum Cat Herders Videos
Like merkle trees, you can put a large amount of data into a Verkle tree, and
make a short proof ("witness") of any single piece, or set of pieces, of that data
that can be verified by someone who only has the root of the tree.
What Verkle trees provide, however, is that they are much more efficient in proof
size. If a tree contains a billion pieces of data, making a proof in a traditional
binary Merkle tree would require about 1 kilobyte, but in a Verkle tree the proof
would be less than 150 bytes.
Verkle trees replace hash commitments with vector commitments or better still a
polynomial commitment.
Polynomial commitments give us more flexibility that lets us improve efficiency,
and the simplest and most efficient vector commitments available are
polynomial commitments.
The number of nodes needed in a merkle proof is much greater than in a verkle
proof
Stateless Ethereum
The Ethereum world state contains all Ethereum accounts, their balances,
deployed smart contracts, and associated storage, it grows without bound.
The idea of stateless Ethereum was proposed in 2017, it was realised that
unbounded state is problematic, especially in providing a barrier for entry to
people wanting to run nodes.
Increasing the hardware requirements for a node leads to centralisation.
The aim of Stateless Ethereum is to mitigate unbounded state growth.
Two paths were initially proposed : weak statelessness and state expiry:
State expiry:
remove state that has not been recently accessed from the state (think:
accessed in the last year), and require witnesses to revive expired state.
This would reduce the state that everyone needs to store to a flat ~20-50
GB.
Weak statelessness:
only require block proposers to store state, and allow all other nodes to
verify blocks statelessly. Implementing this in practice requires a switch
to Verkle trees to reduce witness sizes.
However according to this roadmap it may make sense to do both together.
State expiry without Verkle trees requires very large witness sizes for proving old
state, and switching to Verkle trees without state expiry requires an in-place
transition procedure (eg. EIP 2584) that is almost as complicated as just
implementing state expiry.
See the full proposal here
The core idea is that there would be a state tree per epoch (think: 1 epoch ~= 8
months), and when a new epoch begins, an empty state tree is initialized for that
epoch and any state updates go into that tree.
Full nodes in the network would only be required to store the most recent two
trees, so on average they would only be storing state that was read or written in
the last ~1.5 epochs ~= 1 year.
Block producers will in addition to the block provide a 'witness' that the data is
required to execute the transactions in the block.
There are two key principles:
Only the most recent tree (ie. the tree corresponding to the current epoch)
can be modified. All older trees are no longer modifiable; objects in older
trees can only be modified by creating copies of them in newer trees, and
these copies supersede the older copies.
Full nodes (including block proposers) are expected to only hold the most
recent two trees, so only objects in the most recent two trees can be read
without a witness. Reading older objects requires providing witnesses.
Suppose the dark-blue object was last modified in epoch 0, and you want to
read/write it in a transaction in epoch 3.
To prove that epoch 0 really was the last time the object was touched, we need
to prove the dark-blue values in epochs 0, 1 and 2.
Full nodes still have the full epoch 2 state, so no witness is required.
For epochs 0 and 1, we do need witnesses: the light blue nodes, plus the purple
nodes that can be regenerated during witness verification.
After this operation, a copy of the object is saved in the epoch 3 state.
Transient storage
See EIP-1153
and discussion
Add opcodes for manipulating state that behaves identically to storage but is
discarded after every transaction.
See overview article
Because the blockchain doesn’t have to store transient data after the
transaction, nodes don’t have to use the disk, making it much less expensive
than storage.
So what is the difference between this and using memory ?
Transient storage is available when calling other contracts, for example with
DELEGATECALL
Pros and Cons
Use cases
Re entrancy locks
Example from Uniswap V2
contract NonCustodialFlashLoans {
struct Borrow {
uint256 lenderStartingBalance;
address lender;
IERC20 token;
}
// TSTORE it!
borrower = msg.sender;
--source-minter <SOURCE_MINTER_ADDRESS>
--source-blockchain ethereumSepolia
--destination-blockchain avalancheFuji
--destination-minter <DESTNATION_MINTER_ADDRESS>
--pay-fees-in Native
Huff
Repo
"Huff enables the construction of EVM assembly macros - blocks of bytecode
that can be rigorously tested and evaluated. Macros can themselves be
composed of Huff macros.
Huff doesn't hide the workings of the EVM behind syntactic sugar. In fact, Huff
doesn't hide anything at all. Huff does not have variables, instead directly
exposing the EVM's program stack to the developer to be directly manipulated."
Huff supports tables of jump destinations integrated directly into the contract
bytecode. This is to enable efficient program execution flows by using jump
tables instead of conditional branching.
A series of blog posts about Huff
Documentation is here
Series of tutorials
Getting started
Huff project template
See https://docs.huff.sh/get-started/project-quickstart/#using-the-template
Compiling the contract
see https://docs.huff.sh/get-started/compiling/#compiling-contracts-with-the-
huff-compiler
Example approve
Resources
Huffmate
Plugin for VSCode
Compiler built in rust
huff-rs
Foundry Library
Library
Install with
curl -L get.huff.sh | bash
forge install huff-language/foundry-huff
Huff Macros
There are only two fundamental building blocks to a Huff program:
Macros
Jump tables (and packed jump tables)
Example Macros
template <p1,p2>
#define macro POINT_DOUBLE = takes(3) returns(3) {
<p1> dup3 callvalue shl
swap3 dup4 mulmod
<p2> dup2 callvalue shl
dup2 dup1 dup1 dup4 dup10
mulmod dup2 sub swap8
dup1 mulmod 0x03 mul
dup2 dup2 dup1
mulmod dup9 callvalue shl add swap8
dup9 add mulmod swap3 mulmod add swap2
<p2> swap2 mulmod <p1> sub
}
jump_one:
0x100 0x00 mstore
0x20 0x00 return
jump_two:
0x200 0x00 mstore
0x20 0x00 return
jump_three:
0x300 0x00 mstore
0x20 0x00 return
jump_four:
0x400 0x00 mstore
0x20 0x00 return
err:
0x00 0x00 revert
}
switch_test:
SWITCH_TEST()
}
Constants in Huff
#define constant NUM = 0x420
#define constant HELLO_WORLD = 0x48656c6c6f2c20576f726c6421
#define constant FREE_STORAGE = FREE_STORAGE_POINTER()