## How does Ethereum work?
- [link](https://www.preethikasireddy.com/post/how-does-the-new-ethereum-work)
## ABI encoding
- **abi.encode** encodes its parameters using the ABI specs. Parameters are padded to 32 bytes. For making calls to a contract
- **abi.encodePacked** encodes its parameters using minimal space required by the type. Encoding an uint8 will use 1 byte. It is used when you want to **save space** & not for contract call
- **abi.encodeWithSelector** - same as encode but selector is the first parameter, use when don't want to calc method hash.
- `bytes memory data = abi.encodeWithSelector(FUNC_SELECTOR, _param1, _param2);
`
## Best practice
## Deploy or chained RPC scripts
- Since chained RPC calls can fail at any step, it's a good practice to 1. debug and log on each RPC call, and 2. for deployment to separate into different steps that can be continued
## Bits
### Bitmaps
- [ref](https://soliditydeveloper.com/bitmaps)
- Tricks
- Instead of storing bool arrays, use uint (size depends on the length of array)
### Bit masks
- [explanation](https://youtu.be/9nDYYc_7sKs?si=zp5fmjJ35JNt9eWG&t=1200)
- Used for settings
## BloomFilter
- Events in the ethereum system must be easily searched for, so that applications can filter and display events However, storage space is expensive, so we don't want to store a lot of duplicated data - such as the list of transactions, and the logs they generate. The logs bloom filter exists to resolve this.
- When a block is generated, the addresses of any logging contract, and all the indexed fields from the logs are added to a bloom filter, which is included in the block header. The actual logs are not included in the block data, to save space.
- When an application wants to find all the log entries from a given contract, or with specific indexed fields (or both), the node can quickly scan over the header of each block, checking the bloom filter to see if it may contain relevant logs. If it does, the node re-executes the transactions from that block, regenerating the logs, and returning the relevant ones to the application.
## Bridges
### Security
- Onchain vulnerability
- smart contract vulnerability
- social engineering (including getting users to sign malicious sig or tx)
- Offchain vulnerability
- Validators hacked (centralization)
- Key mismanagement
### Types
#### Bridge function types

#### Bridge designs

Bridge types, As of September 14, 2021
- External validators
- There is usually a group of validators that monitor an address on the source chain and, upon consensus, perform an action on the destination chain. An asset transfer is typically done by locking up the asset in the source chain and minting the equivalent amount of that asset on the destination chain
- e.g. Thundercore bridge
- Validator on the source chain picks up an event
- Relays info and signs a tx on the dest. chain, with sig
- Other validators do the same, until the number of validator nodes pass the consensus threshold count
- Dest. chain validator issues dest. chain token (mint)
- Downsides
- Trust in the centralized group of validators
- Light clients & relays
- Smart contracts with foreign bridge censensus-checking functionality are deployed on each blockchain. Users first need to request for token transfers. The request then will be generated in the form of an inclusion proof (contains chain block header, and the request tx), and users need to submit to contracts on the dest chain for verification. Relayers (decentralized) will then relay the actual blockchain info to the dest chain contract to earn income. Finally, the dest chain contract will verify and process user requests.
- e.g. Rainbow Bridge of the NEAR Protocol
- Upside: decentralized relayers, anyone can setup
- Downside:
- smart contract risk higher
- reply on fraud proofs, which can be slow
- [ref](https://blog.teleportdao.xyz/bridges-validator-vs-relay-b8836b4b4cf0)
- Liquidity networks
- Each node acts like a *router* that holds assets from LP providers. Through locking and dispute mechanism to guarantee security of assets. Users can directly *swap* asset from one chain to another
## Casting (Type casting)
- Casting: bytes && uint downsize casting will truncate overflowing data.
``` Solidity
address (20 bytes) = uint160 = bytes20
```
- Type conversion [ref](https://betterprogramming.pub/solidity-tutorial-all-about-conversion-661130eb8bec)
- Uint - when converting to smaller types, left digits are removed
- `uint32 a = 0x12345678; uint16 b = uint16(a); // b = 0x5678`
- Bytes - when converting to smaller types, right most bytes are discarded
- `bytes2 a = 0x1234;`
- `bytes1 b = bytes1(a); // b = 0x12`
## Contract Code
- [Good read explaining all about code](https://blog.openzeppelin.com/deconstructing-a-solidity-contract-part-ii-creation-vs-runtime-6b9d60ecb44c)
### Bytecode (aka. creation bytecode)
- *Generates* smart contracts
- In hardhat, bytecode includes:
`<init code> <runtime bytecode> <constructor parameters>`
- Solidity:
`type(ContractName).creationCode`
### The init code
- Deploy constructor arguments
- In addition, the user can send native currency to the contract as soon as it is created, and so it is necessary to verify that the contract accepts native currency. This check runs at deploy time, and contract creation should revert if the creator sends native currency to a contract that cannot accept native currency.
- Example of init code:
```
// allocate free memory pointer
PUSH1 0x80
PUSH1 0x40
MSTORE
// length of the runtime code
PUSH1 0x3f
DUP1
// where the runtime code begins
PUSH1 0x11
PUSH1 0x00// copy the runtime code from calldata into memory
CODECOPY
// runtime code is deployed at this step
PUSH1 0x00
RETURN
INVALID
```
### Runtime bytecode (aka deployedBytecode)
- This is the code that is stored on-chain that describes a smart contract. This code **does not include the constructor logic or constructor parameters of a contract**, as they are not relevant to the code that was used to actually create the contract.
- Contains **constant** and **immutable** vars
- Solidity: `type(ContractName).runtimeCode`
- The `getCode()` of JsonRPC
## Design & Architecture
- Think about how to modulize the code. For example, Olympus Dao separates user module to NoteKeeper.sol for storing user data (payment, etc.), use the treasury module to track for revenue...
- System architecture example **Olympus Dao** (V3 contracts): Main *kernel.sol* contract that acts as a central component. Extensions to the kernel are known as *modules*, which can be added or removed by an *executor*. The application-level logic are modulized to what's called *policies*, and there's a modifier called *permissioned* that checks the permission of a policy's function to call for a module's functionality.
- `Using for` pattern for libraries' custom types to achieve methods. Makes contracts more structured and modular, without having to deploy auxiliary contracts
### Design Pattern
#### Factory
- Used to create new contracts
#### Singleton
- Ensures the contract only has one instance
- Example: SafeProxy uses singleton pattern to ensure that there's only one impl for the safeProxy to *delegateCall*
## EIP
### 1559
- Before: Relies on get-price auction, which is ineffective. When network is busy, prices shoot high with big gaps
- Didn't have a good pricing rule so the auction is price-inefficient. Users tend to overprice their tx
- vulnerable to MEV and manipulative bots
- After: removed the gas-price auction, and replaced with base fee. Gas now calc based on network congestion
- User pay network fee (to miner) + base fee (burned) + tip (miner) to priortize tx
## Documentation
- `@inheritdoc` for contract inheritance
## ERC
### ERC-20
#### permit
```
// EIP-2612: keeps track of number of permits per address
mapping(address => uint256) public _nonces;
/**
* @dev Allows for approvals to be made via secp256k1 (elliptic curve) signatures.
* @param owner The owner of the funds
* @param spender The spender
* @param value The amount
* @param deadline The deadline timestamp, type(uint256).max for max deadline
* @param v Signature param
* @param s Signature param
* @param r Signature param
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public {
require(block.timestamp <= deadline);
uint256 ownerNonce = _nonces[owner];
bytes32 permitDataDigest = keccak256(
abi.encode(PERMIT_TYPEHASH, owner, spender, value, ownerNonce, deadline)
);
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), permitDataDigest)
);
require(owner == ecrecover(digest, v, r, s));
_nonces[owner] = ownerNonce.add(1);
_allowedFragments[owner][spender] = value;
emit Approval(owner, spender, value);
}
```
- Nonce is integrated as part of the digest to prevent replay attacks
- `nonce` is included in the signed message, so that it can be checked with the nonce in `digest` in the permit function to guard against replay attack
- Use the permit function so users can leverage others' gas to approve for using his/her tokens.
- How it works: 1. users sign off-chain msg to approve, 2. other person calls the permit fn w/ user's msg to make the approval
- See [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612)
#### safeERC20
- A wrapper library around ERC20 to handle different ERC20 impls. To verify that calls made to the token contracts are successful or not
- [See here](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol#L110)
``` solidity
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
// the line below is basically: (bool success, bytes memory returndata) = target.call{value: value}(data);
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
```
### ERC-165
- can be used to see if function(s) are supported
- [link](https://ethereum.stackexchange.com/questions/40984/how-do-i-check-in-solidity-if-interface-is-supported)
### ERC-721A
- An improved impl of IERC721 to reduce the costs of minting multiple tokens
### ERC-1967: Proxy Storage Slots
- A consistent location where proxies store the address of the logic contract they delegate to, as well as other proxy-specific information.
- On constructor call it will set the new impl via `upgradeToAndCall`
```
IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)
```
### ERC-4337 Account Abstraction
- Enables creating ERC-4337 wallets
- Allows for secure wallets without seed phrases
- ERC-4337 introduces other friendlier wallet protection and logins, such as two-factor authentication, biometrics etc., familiar with traditional banking practices. These are user-friendly and may boost adoption.
- Architecture [link](https://www.erc4337.io/docs/understanding-ERC-4337/architecture)
- Diagram


## Error handling
### Revert
- Used to revert a tx, refunds remaining gas
- after **0.8.4**, [custom errors](https://soliditylang.org/blog/2021/04/21/custom-errors/)
- Also, `require(condition, "error message") ` should be translated to `if (!condition) revert CustomError()`. - [link](https://soliditylang.org/blog/2021/04/21/custom-errors/)
### Require
- Used in the beginning to check if conditions are met. If require returns false, remaining gas will be refunded to the user
### Assert
- Used at the end to handle any unexpected errors or conditions
- :warning: Uses all the gas of a call (run the entire code), so should be used at the end of a function
## Events
- [Understanding events](https://medium.com/mycrypto/understanding-event-logs-on-the-ethereum-blockchain-f4ae7ba50378)
## Functions
- Function selector: first 4 bytes (8 chars in hex) of the `keccack256` hash of the function signature i.e.
```
Function signature: tryThis(uint256,string[])
Function selector: keccak256(signature) = 0x7f6ca090
```
### Calculate function selector
- [link](https://ethereum.stackexchange.com/questions/137436/what-is-a-functions-function-signature-if-it-uses-a-custom-type-stuct-enum)
- Tool (Recommended) Install `cast`, `cast sig 'getName()'`
- It's encoded by the function name and its types (no spaces in between), for example
- For struct or tuple, wrap in `()`
- enum: `uint8`
- tuple array (see below)
``` solidity
getBlah(uint256,bool)
struct Person {
age uint8
...
registered bool
}
getPerson((uint8,...,bool), bool...)
// tuple array
getPersons((uint8,...,bool)[],bool...)
```
### Fallback vs receive
- both are payable functions that receive ether
- Fallback is invoked when contract is called with some ether and function sig cannot be matched in the contract
- Receive is invoked when contract is receiving ether without tx data
### Read functions
- View: Reads state variables
- Pure: Do not reads state variables
### Visibility
- Public: Can be called from internally and externally. Public copies arguments to memory, thereby using more gas than external. The reason for memory copy is because public includes internal calls, which are executed via jumps in the code, and the arguments are passed internally by pointers to memory
- External: Can only be called externally. Saves more gas than public, since it reads arguments from calldata. Cannot be called internally like f(), but can use this.f() to call instead. But this.f() behaves like an external call, which is expensive. [See more](https://ethereum.stackexchange.com/questions/19380/external-vs-public-best-practices)
- Internal: Can only be called internally or by inherited contracts
- Private: Can only be called internally but not by inherited contracts
## Gas
### 21000 base fee
- A normal transaction sending ETH or a token normally costs **21,000** gas, whereas an ERC-20 token approval requires 45,000
- [why 21000](https://ethereum.stackexchange.com/questions/34674/where-does-the-number-21000-come-from-for-the-base-gas-consumption-in-ethereum)
### Gas savings
- **be careful** Ignore safe maths — unchecked{}
- optimizer: The less “runs” set in the optimizer (let’s say 200), the more effect it will have on deployment cost. On the other hand, the more runs (let’s say 10,000), the more effect it will have on function call costs
- For optimizing gas costs, always use Solidity optimizer. It’s a good practice to set the optimizer as high as possible just until it no longer helps reducing gas costs on function calls. This can be recommended because function calls are intended to be executed way more times than the deployment of the contract, which happens just once.
- Operators: and/or
- When these operators are used, place the ones that cost less gas to be evaluated first.
- Or: aa || bb, if aa is true, then compiler doesn't need to check for bb
- And: aa && bb, if aa is false ..
- `selfDestruct` for refunds
- Sometimes cheaper to use bytecode directly, such as declaring constant method selectors `bytes4 constant Itransfer = 0x...`
### Arrays
- Based used for arrays with small, packable data size i.e. `uint8` since multiple items can be concatenated in storage
- caching array length, in a for loop:
```solidity
// do this
uint256 len = _data.length;
for (uint256 i = 0; i < len; i++) {
// Perform some operations
}
// not this, which reads data for length on every iteration
for (uint256 i = 0; i < _data.length; i++) {
// Perform some operations
}
```
- loop unrolling in certain scenarios to reduce loop cycles
### Functions
- Minimize external function calls wherever possible. The less the contracts call other contracts, the more gas you will save
- Function names
- The Solidity compiler will sort all the functions in a contract by their selector (in hexadecimal order) and will go through each of them when executing any function call to check which is the function selector called. Going through each of the function on a contract will cost 22 gas.
- Therefore, should place the functions most frequently called in the front

- Payable functions are cheaper than non-payable ones because for the non-payable ones, the contracts need to have some extra op-codes to be ready to check if another contract or an external account is trying to send ETH to it, and if so, revert the transaction.
### Storage
- Minimize state changes unless absolutely necessary, and consider using local variables where appropriate.
- try to compact data into 32 byte (256 bit) slots
- Timestamp
- `uint32`, 4294967295, which will be hit by Unix time on February 7, 2106.
- use constants or immutables when you can. (Their values are instead compiled directly into the smart contract bytecode, whereas storage variables require SLOAD)
- (warm access) use memory to **cache** storage variables in a function when you need to refer it multiple times (to avoid SLOADs)
- Init with non-zero values. Changing a storage value from a zero value to non-zero val will cost 20000 gas and all non-zero nth writes will cost 5000 gas. i.e.
- The reason is the state tree gets bigger, whereas on deletion it sets to default value
`uint256 _data = 1; instead of uint256 _data;`
- Opposite of above. Changing var from non-zero to zero value will get you gas refund
- Deleting unused variables doesn’t mean “deleting” them, as it would cause all sorts of issues to pointers in memory - it's more like assigning a default value back to a variable, that when done, grants you a 15,000 units Gas refund.
- For example, issuing a delete to a uint variable simply sets the variable's value to 0.
```Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract WithDelete {
uint public data;
function useDelete() public {
data = 123; // Example operation
delete data; // Reset data to its default value
}
}
```
## Hardhat
### Console
- get contract: `const myContract = await hre.ethers.getContractAt("MyContract", contractAddress);
`
## Inheritance
- Multi-inheritance languages face the diamond problem:
when two classes B and C inherit from A, and class D inherits from both B and C. If there is a method in A that B and C have overridden, and D does not override it, then which version of the method does D inherit: that of B, or that of C?

- In order to solve the diamond problem, Solidity uses a linearization approach like Python. However, the order is reversed, and should be from **most base-like** to **most derived**
- When a function is called that is defined multiple times in different contracts, the given bases are searched from right to left in a depth-first manner, stopping at the first match
## Library
- Good for util functions
- Doesn't have to be inherited, and can be use after importing
- cannot have state variable
## Lib and utils (Solidity)
- SafeCast: require that casting does not overflow
## Merkle proof
- Example: proving K is in the tree
- Need H(L), H(IJ), H(MNOP) and H(ABCDEFGH), without having to reveal K or any of the data. In other words, verifier knows only root, to prove membership they have to supply leaves along the path from given member towards root

- Usage:
- If the data belong in the tree
- airdrops: [merkleDistributor.sol](https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol)
- Need to pre-define accounts & amounts, and create according merkle root
- sets root on constructor call
- making proofs while preserving privacy
- Store merkle root in contract
- Proof should be some off-chain data used to prove that a leaf is part of the tree
- - ref: [complete explanation](https://soliditydeveloper.com/merkle-tree)
```solidity
function prove(address sender, bytes32[] memory proof) private view returns (bool) {
if (s_merkleRoot == bytes32(0)) return true;
return MerkleProof.verify({
proof: proof,
root: s_merkleRoot,
leaf: keccak256(bytes.concat(keccak256(abi.encode(sender))))
});
}
```
### Prove non-membership
- Challenge: How to prove that a node **does not** exist in the merkle tree?
- Answer: **sorted merkle tree**
- To prove that 5 is not in the tree in the example below, provide the proofs of membership of 4 and 6. And because 4 and 6 are in successive order, 5 cannot possibly exist in the tree.
- [detailed explanation](https://crypto.stackexchange.com/questions/31914/proof-of-non-membership-on-a-merkle-tree)
```
R
/ \
N N
/ \ / \
N N N N
/ \ / \ / \ / \
0 2 3 4 6 7 8 9 <-- values
0 1 2 3 4 5 6 7 <-- binary index
```
### Merkle tree in Ethereum

- In a Merkle tree, the leaf nodes contain the hash of a tx, and the root node is the block hash.
- Summary
- We don't need to compare all the data across the leaf nodes to know if they have the same data. We can just compare the root node hash.
- We only need to store the root hash of the tree representing the block on the blockchain (as opposed to storing all the data in the blockchain) and still keep the data immutable.
### Merkle proof in Solidity
- [Openzepplin lib](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
- Make sure that the hashing algorithm should be **keccak256** and **pair sorting** should be enabled.
## Naming Convention
``` Solidity
/* From Openzeppelin's contracts */
address private _privateAddr;
address public publicAddr;
constructor(address privateAddr_, address publicAddr_) {}
// function with params
function _privateFn(address param1) private {}
function _internalFn() internal {}
function externalFn() external {}
function publicFn() public {}
```
## OpCodes
### Create
```solidity
new_address = hash(sender, nonce)
```
### Create2
- Equation: `keccak256( 0xff ++ account ++ salt ++ keccak256(init_code))[12:]
`
- Uses `init_code`, which includes bytecode & constructor params & constants...
## Proxy & Upgradeable Contracts
- UUPS & transparent upgradeable proxies follows [ERC-1967](#ERC-1967-Proxy-Storage-Slots) standard
#### Best practices
- :warning: can use constants and immutables but they will be on the impl. contracts' runtime bytecode so don't self-destruct, otherwise they will be gone forever
#### Storage layout
- Upgradeable contracts have to have consistent storage layout
- Two techniques to add new storage vars
- Reserved slots (note: no increased gas usage)
- [Namespaced Storage for better upgrades security](https://blog.openzeppelin.com/introducing-openzeppelin-contracts-5.0)
``` Solidity
// Reserved slots
contract Base {
uint256 base1;
uint256[49] __gap;
}
contract Child is Base {
uint256 child;
}
// base contract upgrade, reduce # of fixed-size arr
contract Base {
uint256 base1;
uint256 base2; // 32 bytes
uint256[48] __gap;
}
```
``` solidity
// Namespaced storage
struct XStorage {
uint256 version;
}
function _getXStorage()
internal
pure
returns (XStorage storage $)
{
assembly {
$.slot := XStorageLocation
}
}
function get() internal view override returns (string memory) {
XStorage storage $ = _getXStorage();
return $.version;
}
```
#### In OpenZeppelin, storage slot:
- *0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc*
- obtained as bytes32 <br>(uint256(keccak256('eip1967.proxy.implementation')) - 1)
### Openzeppelin Proxy
- Proxy contract: an abstract contract implementing delegation functionalities
- Implements _delegate function & fallback delegation
- ERC1967Proxy
- Inherits rom Proxy.sol above
- constructor (calls upgradeToAndCall to set impl and call initial functions calls if specified in data).
- Transparent upgradeable proxy
- This proxy is usually paired with a proxyAdmin contract, and only the owner of the proxyAdmin can upgrade the proxy
- _admin: a proxyAdmin contract, which implements *upgradeAndCall* to upgrade impl of a proxy. ProxyAdmin can manage multiple proxies and their impls. Admin can only be used for upgrading the proxy
- all function calls go to the `fallback` function defined in `Proxy.sol`, which calls `_fallback` that is overriden in Transparent proxy. `_fallback` writes that only admin can call `upgradeToAndCall` otherwise do `Proxy.sol`'s `_fallback`, which exec the corresponding function in implementation
- Openzepplin transparent proxy deployment
- Deploys impl
- Deploys a proxy admin
- Deploys transparent proxy & sets impl
- UUPS proxy
- _authorizeUpgrade: a contract that inherits from UUPS **needs** to implement this function in order to add access control to proxy upgrades. Example: add an *onlyOwner* modifier
- Openzepplin UUPS proxy deployment
- Deploys the logic contract
- Deploys an ERC1967Proxy (see above), and sets the impl as the logic contract
- **UUPS vs Transparent**
- Transparent Proxy 有關升級的邏輯都放在 Proxy 裡面,而 UUPS Proxy 則是透過inheritance讓升級的邏輯和一般的邏輯一起放在 Logic Contract。
- Beacon Proxy [here](https://docs.openzeppelin.com/contracts/3.x/api/proxy)
- Beacon proxy is a proxy pattern in which multiple proxies refer to a single smart contract to give them the address of the implementation contract. The contract which gives the implementation contract address to the proxies is called a beacon contract.
- A beacon proxy is used when you have multiple proxies referring to a single implementation contract which is upgraded as we go along. If you were to use a Transparent proxy or UUPS Proxy you would have to upgrade the implementation contract address in all the proxies one by one. If your project needs multiple proxies referring to the same implementation then beacon proxy is good option to go with.
## References
- [EVM op codes](https://www.evm.codes/?fork=shanghai)
- [opCreate3](https://medium.com/taipei-ethereum-meetup/create3-deploy-contract-multichain-c92de4241614)
- [OpenChain - Signature database, transaction tracer, abi tools](https://openchain.xyz)
- [Solidity by example](https://solidity-by-example.org/)
## Sending Ether
- :warning:**Do not** use *transfer* with upgradeable contracts, use .call instead. See [here](https://forum.openzeppelin.com/t/how-to-receive-ether-to-an-upgradeable-contract-from-a-contract-using-msg-sender-transfer/5390/2)
### transfer
- the receiving contract needs to have *fallback* or *receive*
- gas limit: 2300 for safety
- Throws error
### send
- similar to transfer, returns the status as a *boolean* value
### call
- It is the recommended way of sending ETH to a smart contract. The empty argument triggers the fallback function of the receiving address
- Forwards all gas or set a gas limit
:warning: Vulnerable to reentrancy and denial attacks
:star: **make sure to handle return values**. return: (bool sent, bytes memory data)
## Security
### Security Practices Checklist
- before 0.8.x: overflow & underflow
- reentrency w/ reentrency guard
- delegateCall
- Can be dangerous in calling arbitrary contracts
- Example: A deployer uses `create2` to deterministically create contracts. A legit contract was approved, but it called `selfdestruct` on itself and hacker deployed another attack contract using `create2` to get the same adddress. Then `execute` will execute from the attack contract and steal owner
- Real case: [Tornado cash governance hack](https://medium.com/coinmonks/tornado-cash-governance-hack-ec77ebb3aa68)
- How to fix
- Avoid `delegateCall`
- Put timelock on Dao contracts
- Verify the proposal's bytecode
``` Solidity
// Victim contract
Contract Dao {
address owner;
...
function execute(uint256 proposalId) external payable {
...
(bool ok, ) = proposal.target.delegatecall(
abi.encodeWithSignature("executeProposal()")
);
require(ok, "delegatecall failed");
}
}
contract Deployer {
event Log(address addr);
function deployProposal(uint256 salt) external {
address addr = address(new Proposal{salt: salt}());
emit Log(addr);
}
function deployAttack(uint256 salt) external {
address addr = address(new Attack{salt: salt}());
emit Log(addr);
}
}
contract Proposal {
event Log(string message);
function executeProposal() external {
emit Log("Executed code approved by DAO");
}
function emergencyStop() external {
selfdestruct(payable(address(0)));
}
}
contract Attack {
event Log(string message);
address public owner;
function executeProposal() external {
emit Log("Executed code not approved by DAO :)");
// For example - set DAO's owner to attacker
owner = msg.sender;
}
}
```
- msg.sender > tx.origin
- sending ether: rejections attack. Some smart contracts don't **receive** ether, can use wrapped token instead
- Signature Malleability
- vulnerability in `ecrecover` since can be valid for two values on the elliptic curve
- [explanation](https://0xsomeone.medium.com/b002-solidity-ec-signature-pitfalls-b24a0f91aef4)
- [video explanation](https://www.youtube.com/watch?v=V3TJLDHZBFU&t=842s)
- Use [ECDSA.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) to check signatures
- Using **safeCast.sol** for casting ints safely
- At times when precision is required such as calculating rewards, we need to round number off in order to avoid rounding errors. Solution: **trim the remainders**. e.g.
``` Solidity
uint256 remainder = amount % RewardLib.REWARD_PRECISION;
if (remainder > 0) {
amount -= remainder;
i_LINK.transfer(sender, remainder);
}
```
- Avoid using extcodesize to check for Externally Owned Accounts. Contract constructors don't have codesize yet
### Vulnerabilities
#### Compound-related
- Reentrncy with ERC677/ERC777
- [TUSD](https://medium.com/chainsecurity/trueusd-compound-vulnerability-bc5b696d29e2) (multiple entry points)
- TUSD has two contracts (one legacy) that read the same balance. In Compound, one contract is the underlying token, but the other can be passed in to `sweepToken` and will transfer all balance to admin.
- Even though it transfers to admin, but `cTUSD` will be affected since the exchange rate is determined based on the amount of underlying token
``` solidity
function sweepToken(EIP20NonStandardInterface token) override external {
require(address(token) != underlying, "CErc20::sweepToken: can not sweep underlying token");
uint256 balance = token.balanceOf(address(this));
token.transfer(admin, balance);
}
```
#### Ethernaut
- `selfDestruct` to force send ether to your contract. Be careful if your contract should not receive ether
#### Short address attack
- [See here](https://www.reddit.com/r/ethereum/comments/6r9nhj/cant_understand_the_erc20_short_address_attack/)
## Storage
)
- Gas usage: storage > memory > calldata
### Calldata
- An **immutable**, temporary location where **function arguments** are stored
- Encoding (every slot is 32 byte):
- `0x` + `4 byte of function sig` + `data`
- Format, see example below
- fixed data goes right on sequentially
- Variable length data
- First slot stores the start location (bytes offset) of the data
- Start location stores the size or the length of data
- Following the start location is the data itself
- `msg.data`
- Example:
Given the following method:
``` solidity
function getMsgData(
address _address,
bytes _bytes,
uint _int,
uint[] _array,
string _string
)
external
returns (bytes)
{
return msg.data;
}
```
If we call this method the following params:
```js
contract.getMsgData(
someAddress,
web3.toHex('my bytes'),
12,
[1, 4, 412],
'thisislargerthanthirtytwobytesstring'
);
```
The response
``` txt
0x
d1621754 // (1) methodId
000000000000000000000000c6e012db5298275a4c11b3e07d2caba88473fce1 // (2) "_address"
00000000000000000000000000000000000000000000000000000000000000a0 // (3) location of start of "_bytes" data (item 7) = 160 bytes
000000000000000000000000000000000000000000000000000000000000000c // (4) "_val" = 12
00000000000000000000000000000000000000000000000000000000000000e0 // (5) location of start of "_array" data (item 9) = 224 bytes
0000000000000000000000000000000000000000000000000000000000000160 // (6) location of start of "_string" data (item 13) = 352 bytes
0000000000000000000000000000000000000000000000000000000000000008 // (7) size of "_bytes" data in bytes (32 bytes)
6d79206279746573000000000000000000000000000000000000000000000000 // (8) "_bytes" data padded to 32 bytes
0000000000000000000000000000000000000000000000000000000000000003 // (9) length of "_array" data = 3
0000000000000000000000000000000000000000000000000000000000000001 // (10) _array[0] value = 1
0000000000000000000000000000000000000000000000000000000000000004 // (11) _array[2] value = 4
000000000000000000000000000000000000000000000000000000000000019c // (12) _array[3] value = 412
0000000000000000000000000000000000000000000000000000000000000024 // (13) size of "_string" data in bytes (64 bytes)
7468697369736c61726765727468616e74686972747974776f6279746573737472696e670..0 // (14) "_string" data padded to 64 bytes
```
- [above example link](https://ethereum.stackexchange.com/questions/14037/what-is-msg-data)
### Memory
- The free memory pointer `0x40` holds the position of the first unallocated memory position. Assuming there are no in-memory variables before this snippet, the first free memory slot is at position 0x80 (decimal 128, beginning of the 5th 32byte slot).
- Meaning mload(0x40) (reads the value of the pointer located at 0x40) returns 0x80 (the value of the pointer).
#### Layout
- **memory vs calldata**
- Memory is reserved for variables that are defined within the scope of a function
- whereas calldata is immutable for function arguments
- [layout explained](https://www.rareskills.io/post/ethereum-contract-creation-code)

The first four slots are used for specific purposes:
- The first two slots (0x00 - 0x3f, 64 bytes) is like a scratch pad for Solidity to do calculations. It's a temporary space to store data while it's being worked with
- The first 32 bytes (0x00 to 0x1f) are used to store the length of dynamic data like strings or bytes arrays
- The second two slots (0x40 - 0x5f, 32 bytes) is used to keep track of how much memory is currently being used
- Initially points to 0x80. This means that when the contract starts, it's ready to start using memory from the 0x80 slot onwards.
- The third slot (0x60 - 0x7f, 32 bytes) is a special slot that is always set to zero. It's like a default setting that Solidity uses when it needs a zero value
- Should never change the data in this slot. It's always set to zero and is used as a default value for dynamic memory arrays
### Stack
- Stack has a maximum depth of 1024 elements and supports the word size of 256 bits
- EVM can only access the 16th topmost items on the stack
- Stack too deep error, in order to reduce the number of vars, can do local scope like so:
``` solidity
uint var1;
{
(uint varA, uint varB) = getVars();
var1 = varA + varB;
}
// now use var1
```
### Storage
- [docs](https://docs.soliditylang.org/en/v0.8.21/internals/layout_in_storage.html#storage-inplace-encoding)
- including how arrays and mapping are stored
- max. size is 2²⁵⁶-1
- 32 byte slots
- Can be stored compactly. Solidity will follow storage layout to find variables, For example:
```
// will take two slots
uint128 a
uint128 b
uint256 c
// will take three slots
uint256 a
uint256 b
uint256 c
```
- *Terminology* **lower-order alignment**: in Solidity storage layout starts from the left of the slot to the right, following the big-endian format
- Tip: can pass storage variables into function param to update data, see below code:
``` Solidity
function _push(
uint256[] storage store
) private {
store.push(...)
}
```
#### Dynamic data storage
- Dynamic data (strings, dynamic arrays): Occupies in 1 slot (variable slot) if the data + length can be stored as one slot. Otherwise, the variable slot in storage stores the **length of the array**. The **values** in the array are stored consecutively starting at the hash of the slot.
- mapping: The value of key k is located at `keccak256(h(k) . slot)` where a hash function is applied to the key depending on its type:
- for value types, h pads the value to 32 bytes in the same way as when storing the value in memory.
- for strings and byte arrays, h(k) is just the unpadded data.
See [here](https://coinsbench.com/solidity-layout-and-access-of-storage-variables-simply-explained-1ce964d7c738) for storage layout explained w/ examples
## Signature
- [Sigining methods](https://soliditydeveloper.com/ecrecover)
- eth_sign (can sign anything, even txs, **dangerous**)
- personal_sign prefix signature w/ `\x19Ethereum Signed Message`, **cannot** be used to sign tx. Great for user sign-ins
- EIP-712 (the standard), current mainstream version is *signTypedData_v4*. The purpose is so user knows what is being signed. See [here](https://medium.com/metamask/eip712-is-coming-what-to-expect-and-how-to-use-it-bb92fd1a7a26) for more
- Usage: for verifying sig on-chain efficiently
- Need to setup domain_separator, see example below
- [Usage recommended by Metamask](https://docs.metamask.io/wallet/how-to/sign-data/)
```
/**
* @return The computed DOMAIN_SEPARATOR to be used off-chain services
* which implement EIP-712.
* https://eips.ethereum.org/EIPS/eip-2612
*/
function DOMAIN_SEPARATOR() public view returns (bytes32) {
uint256 chainId;
assembly {
chainId := chainid()
// to get underlying chain ID
}
return
keccak256(
abi.encode(
EIP712_DOMAIN,
keccak256(bytes(name())),
keccak256(bytes(EIP712_REVISION)),
chainId,
address(this)
)
);
}
```
- The {r, s, v} signature can be combined into one 65-byte-long sequence: 32 bytes for r, 32 bytes for s, and one byte for v
## supportsInterface
- Have a look at `ERC165Checker.sol`
## Testing
### Tests
- unit tests
- functional (how modules are being used) or e2e (entire flow) tests
- fuzzy tests (foundery or [echidna](https://github.com/crytic/echidna))
## Transaction Lifecycle
- [eth docs](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/#transaction-execution-ethereum-pos)
- User signs a transaction and sends it
- Does the proper encoding
- Signs the tx w/ the private key
- W/ tx data, user nonce, to, from, value, gas...
- Tx gets created (Ethers.js)
- populateTransaction
- set provider
- get gas price...
- sign tx
- broadcast tx
- get block #, network
- creates a transaction by calling *eth_sendRawTransaction*, get the hash as response
- Inside Geth
- `SendTransaction` in `api.go` gets called
- `api_backend.go's SendTx` will add the tx to the tx pool
- See [TxPool](#TxPool)
- Consensus Engine
- Monitors the tx pool from Geth. After it picks up new tx(s), will validate the tx(s) and generate a new block
## TxPool
- When geth's backend is started, it created a new tx pool `eth/backend.go: New()`
- The init txpool starts a loop (running on goroutine) that subscribes chain events `core/txpool/txpool.go: loop()`
## Units
- Uint: same as uint256
## Using
- [ref](https://medium.com/coinmonks/soliditys-using-keyword-c05c18aaa088)
- Allow data types to use a library
- The data type that uses the lib function becomes the first variable
## Variable
### Dynamic array
- It is no longer possible to resize storage arrays by assigning a new value to their length (see [alien codex](https://ethernaut.openzeppelin.com/level/19) vulnerability)
## Wallet
### Wallet generation
- [concept link](https://mightyblock.co/blog/seed-phrase-vs-private-key-a-technical-overview/)
- [implementation link](https://hackernoon.com/from-mnemonic-phrase-to-private-key-everything-you-need-to-know)
- Steps
- Mnemonic generation
- Mnemonic to seed conversion
- Seed to master key conversion
- Master key to child private keys conversion
#### Mnemonic generation
``` rust
// 1. Let’s begin by generating random (not super random btw) 128 bits (16 bytes):
let mut bytes = [0u8; 16];
rand::thread_rng().fill(&mut bytes[..]);
// 2. Next, we need to calculate the SHA256 hash of our bytes:
let mut hasher = Sha256::new();
hasher.update(bytes);
let result = hasher.finalize();
```
## YUL
- Yul (previously also called JULIA or IULIA) is an intermediate language that can be compiled to bytecode for different backends.
- It can be used in stand-alone mode and for “inline assembly” inside Solidity.
---
## Interview questions
#### Write a reentrancy function and fix the vulnerability.
#### Explain the storage slots used in EVM. Where are the mapping values stored?
```
Contract ABC{
Uint128 a;
Uint128 b;
Uint256 c;
private mapping(uint256->address) whatever;
}
```
#### Explain the differences of the following visibilities:
External public internal private
#### What are the differences of different types of proxies?
- UUPS
- Transparent
- Beacon
#### ERC721 1155 diff
#### 721 721a diff
#### ERC20 transferFrom & transfer diff
#### How is USDT’s approve different than other approves in implementation?
#### safeTransferFrom & transferFrom diff
#### Oracle Experience
#### Send vs transfer vs call difference
#### How to batchTransfer in Solidity but ensure that contracts can receive tokens successfully?
Trick, if can’t send native, send wrapped tokens
#### How does Compound calc user interests without iterations
#### SNX staking rewards
#### How does inheritance work in Solidity
#### How does pragma and versioning work in Solidity (and caret ^)
[here](https://www.dummies.com/article/business-careers-money/personal-finance/cryptocurrency/basic-ethereum-smart-contract-syntax-263811/)
#### How to compare two strings
keccak256(abi.encodePacked(a))
#### Explain and calculate impermenant loss
#### Can you give 2 examples of different staking mechanisms in DeFi? Explain the economic implications of those differences.
#### What is a rebasing token? Can you give an example and explain how the rebasing works?
#### Can you explain some of the differences between Uniswap V2 and Uniswap V3? (poke at capital efficiency and the NFT LP Token trade-off)
#### Example syntax using ecrecover to verify address
```
function verify(bytes32 hash, uint8 v, bytes32 r, bytes32 s) constant returns(bool) {
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 prefixedHash = keccak256(prefix, hash);
return ecrecover(prefixedHash, v, r, s) == (Your Address);
}
```
#### What is impermanent loss?
Impermanent loss is a concept that arises when you provide liquidity to a decentralized exchange (DEX) like Uniswap. It happens due to the volatility of the assets in the liquidity pool and can be seen as the difference between holding the assets versus staking them in a pool
For example:
Say the price of eth is 100 dai. Say that you `addLiquidity` with 1 eth/100 dai, and the dollar value of that is $200. If you have 10% of the liquidity of the pool, the pool would have 10 eth/1000 dai
When the price of eth rises to $400, the arbitragers will strive to balance the pool accordingly. Now the pool has 5 eth/2000 dai. Given that you own the 10%, you would have 0.5 eth / 200 dai, and the dollar value of $400. But if you have not entered the pool, then you would've kept the 1 eth / 100 dai, which has the dollar value of $500.
#### Can you give 2 examples of different staking mechanisms in DeFi? Explain the economic implications of those differences.
There are ones with a receipt token and ones that keep the books without issuing tokens. The ones that I'm more familiar with are OpenDaoStaking (issues a receipt token) and Synthetix's staking rewards. Differences:
- Transferability
- book keeping by issuing a new receipt token or not
- reward calculation
- receipt token: user amount of receipt token * reward token in pool / total supply of receipt token
- reward distribution
- snx: needs to specify staking period so the protocol will distribute token rewards within the period
- receipt token: no need to specify the staking period. Protocol consumes all the rewards upon *addRewards*, so rewards will need to be added continually