# Nuanced EVM
---
## EVM Trivia
- 0x4: the identity precompile. Returns whatever data gets sent.
- **Question**: What is `extcodesize(0x4)`?
---
## Solidity Trivia
```solidity!
interface MagicReturnCheck {
// MUST return the magic value
// `MagicReturnCheck.foo.selector`
function foo(uint) external returns(bytes4);
}
function magicCheck() {
require(
MagicReturnCheck(address(0x4)).foo(0x1337) ==
MagicReturnCheck.foo.selector
);
}
```
What happens when `magicCheck()` gets called?
---
## `returndatacopy`
- Introduced in Byzantium HF, October 16th 2017.
- Fully allowed dynamic types in ABI!
```solidity
interface Nameable {
function name() view returns(string memory);
}
// Without `returndatacopy`, the size needs
// to be known at compile time. True dynamic
// types were not possible
string memory name = Nameable(...).name();
```
---
## Use case: Minimal Proxy Contract
- One implementation, but several cheap clones. For example, a smart contract wallet.
- A `delegatecall` forwader: copy all the `calldata` and forward it over.
```solidity!
let s := delegatecall(gas(), addr, mPtr, mLength, 0, 0)
// Copy the returndata buffer
returndatacopy(0, 0, returndatasize())
// Based of the value of `s`, `revert` or `return`
// with the returndata.
...
```
---
## Nuance in `returndatacopy`
- Out of bounds access.
- `mload(offset)`, if `offset > msize()`, returns `0`.
- `calldatacopy(mPtr, cdPtr, length)` if `cdPtr + length > calldatasize()`: empty values.
---
## `returndatacopy`
Out of bound access in `returndatacopy(...)` reverts, unlike out of bounds access in `calldatacopy(...)` or `mload(...)` or `calldataload(...)`
---
## How did this happen?
The original proposal suggested treating out-of-bounds reads to use 0 as the value: [EIP-211](https://github.com/ethereum/EIPs/pull/211).
![](https://i.imgur.com/3X2Ksdk.png)
The EIP was updated to the new semantics: revert on out-of-bounds read.
---
## Bugs related to `returndata` buffer: Vyper's Clone
- The old Vyper [`clone()`](https://github.com/vyperlang/vyper/blob/069936fa3fee8646ff362145593128d7ef07da38/vyper/functions/functions.py#L1471) method (`< 0.2.9`).
- Psuedocode in Solidity:
```solidity!
// Hardcoded offset
let s := delegatecall(gas(), addr, memPtr, mLen, 0, 0x1000)
// Returndatacopy was not used!
// either `revert(0, 0)` or `return(0, 0x1000)`
```
- *Problem*: You always get `0x1000` bytes of data.
---
## Always return `0x1000` bytes of data
- The ABI standard allows extra data.
- For `uint x = I(...).f(...)`: only 32 bytes is needed, but returning more is accepted in high level Solidity.
- But some applications likes to be more restrictive.
---
## Always return `0x1000` bytes of data
- Example: some implementations of `SafeERC20` methods. If you used a ERC20 token that was an old Vyper `clone(...)` stricter ABI checks failed: bad for interoperability.
---
## `returndatacopy(...)` bug in solc's optimizer
- The reverting semantics can be tricky to handle in the optimizer.
- There is an optimizer step that removes memory copies if the copied region does not get used.
- `returndatacopy(mPtr, rdPtr, length)`: can we remove this if we know memory between `[mPtr, mPtr + length)` is never read?
---
## The `solc` bug
- The expression `returndatacopy(mPtr, rdPtr, length)` can only be removed if it there is no out-of-bounds `returndata` access.
- `solc` [didn't include](https://github.com/ethereum/solidity/issues/13039) this check.
- Deviation in semantics: unoptimized code can revert, but optimized code will not.
---
## The `solc` bug
- Found while triaging a false positive produced by solc's Fuzzer!
- Low impact: `returndatacopy(x, 0, returndatasize())`.
- Problematic for literal length `returndatacopy(x, y, 32)`, which was rare.
---
## Calling an account without code
```solidity
(bool s, bytes memory ret) = address(addr).call(data);
```
- What happens when `addr` does not have code?
- What is the value of `s`? `true` or `false`?
---
## Calling an account without code
- Low level calls to accounts without code succeeds!
- High level calls will have an `extcodesize(...)` check to prevent this.
- High level calls add `extcodesize(addr) > 0` before the calls.
---
## Can we optimize the `extcodesize` check?
```
(bool s, bytes memory ret) = address(addr).call(data);
```
- `extcodesize(...)`: extra 100 gas since EIP-2929, even more (700) in the past.
- Observation: accounts without code cannot return data (?).
- If `ret` needs to be used, then we may just skip `extcodesize(addr) > 0` check!
---
## The `extcodesize` optimization
- Most external calls will be followed by decoding the `returndata`. `uint a = I(...).f(..);`
- Accounts without code cannot return data. So the ABI decoding will fail in such case.
- `extcodesize > 0` check can be skipped if function returns any data.
---
## Functions that return data are cheaper?
- Yes!
- Because `extcodesize > 0` check can be skipped (since Solidity 0.8.10).
---
## Pattern: return a magic value
- Many EIP standards use a magic value to show success.
---
![](https://i.imgur.com/2aKedkF.png)
---
## Rule: "accounts without code cannot return data"
- Is that really true?
---
## Extcodesize and Precompiles
- `extcodesize`: what should be the `extcodesize` of precompiles?
- Currently `0`, but this is deceiving.
- These are accounts without code, but they can return data!
---
## Back to the Solidity trivia
```solidity!
interface MagicReturnCheck {
// Should return the magic value
// `MagicReturnCheck.foo.selector`
function foo(uint) external returns(bytes4);
}
function magicCheck() {
require(
MagicReturnCheck(address(0x4)).foo(0x1337) ==
MagicReturnCheck.foo.selector
);
}
```
- What happens when `magicCheck()` gets called?
- Before `0.8.10`, it reverts. After `0.8.10`, it succeeds.
---
### What can we learn from this?
- Consistency v/s micro-optimizations.
- Seeing the big picture v/s one small change.
- A lot of rules in EVM has exceptions.
- Design: try to avoid complexity if possible.
{"metaMigratedAt":"2023-06-17T22:00:41.245Z","metaMigratedFrom":"YAML","title":"Nuanced EVM","breaks":true,"contributors":"[{\"id\":\"fb4237dc-a2c5-447b-b203-6f7d1a2af9c5\",\"add\":7201,\"del\":1050}]"}