Skip to content

Commit 4d7c9f2

Browse files
committed
Add low-level calls
1 parent fc4b9a0 commit 4d7c9f2

File tree

1 file changed

+144
-4
lines changed

1 file changed

+144
-4
lines changed

README.md

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ _This cheatsheet is based on version 0.8.29_
136136
- [Function Overloading and Selectors](#function-overloading-and-selectors)
137137
- [Collision Issues](#collision-issues)
138138
- [Call \& Delegatecall](#call--delegatecall)
139+
- [Introduction to Low-Level Calls](#introduction-to-low-level-calls)
140+
- [Why Use Low-Level Calls?](#why-use-low-level-calls)
141+
- [`call`](#call-1)
142+
- [Behavior](#behavior)
143+
- [`delegatecall`](#delegatecall)
144+
- [Behavior](#behavior-1)
145+
- [`staticcall`](#staticcall)
146+
- [Security \& Best Practices](#security--best-practices)
139147
- [Create, Create2, Create3, and CreateX](#create-create2-create3-and-createx)
140148
- [ABI Encode \& Decode](#abi-encode--decode)
141149
- [Bitwise Operations](#bitwise-operations)
@@ -2211,6 +2219,142 @@ contract WontCompile {
22112219

22122220
# Call & Delegatecall
22132221

2222+
## Introduction to Low-Level Calls
2223+
2224+
In Solidity, you can interact with other contracts or addresses at a low level using:
2225+
2226+
- `call`
2227+
- `delegatecall`
2228+
- (less common) `staticcall` (for read-only calls)
2229+
2230+
These are lower-level functions than the usual contract function calls because they bypass the Solidity function name → selector → ABI encoding process (unless you manually encode/decode arguments and return data). They return success/failure booleans and raw byte data rather than automatically reverting on failure or decoding data.
2231+
2232+
### Why Use Low-Level Calls?
2233+
2234+
1. **Dynamic function calls**: If you only know at runtime which contract or function signature you’re calling.
2235+
2. **Proxies**: Common pattern for upgradeable contracts or decoupling logic from storage.
2236+
3. **Manual gas management**: You can specify the exact gas or handle reverts manually.
2237+
2238+
## `call`
2239+
2240+
`call` allows you to call a specified address (contract or EOA) with custom calldata, value (Ether), and an optional gas parameter.
2241+
2242+
```solidity
2243+
(bool success, bytes memory returnedData) = targetAddress.call{value: etherAmount, gas: gasAmount}(calldata);
2244+
```
2245+
2246+
### Behavior
2247+
2248+
- **Does Not Auto-Revert**: If the call fails (e.g., the target reverts), `success` will be `false`. The state changes in your contract up to that point remain unless you manually revert.
2249+
- **Returns Raw Data**: `returnedData` is the raw bytes the target contract returned. You must decode it if you expect a specific type (e.g., an integer or a boolean).
2250+
- **Forwards Gas**: By default, `call` will forward all remaining gas. You can specify a `gas: someAmount` to limit it.
2251+
- **Potential Reentrancy**: Because you may be forwarding a lot of gas, be mindful of reentrancy vulnerabilities if your contract’s state is updated before calling out.
2252+
2253+
```solidity
2254+
function callTransfer(address _token, address _to, uint256 _amount) external {
2255+
// Encode the function selector and arguments for an ERC20 transfer
2256+
bytes memory data = abi.encodeWithSelector(
2257+
bytes4(keccak256("transfer(address,uint256)")),
2258+
_to,
2259+
_amount
2260+
);
2261+
2262+
// Low-level call
2263+
(bool success, bytes memory returnData) = _token.call(data);
2264+
2265+
require(success, "call to transfer failed");
2266+
2267+
// Optionally decode the returned data (many ERC20s return bool, but not all)
2268+
// bool transferSuccess = abi.decode(returnData, (bool));
2269+
}
2270+
```
2271+
2272+
## `delegatecall`
2273+
2274+
`delegatecall` is similar to `call` but crucially **executes the code of the target contract in the context of the caller’s state**. This is the foundation for **proxy** contracts or **upgradeable** contract patterns.
2275+
2276+
```solidity
2277+
(bool success, bytes memory returnedData) = targetAddress.delegatecall(calldata);
2278+
```
2279+
2280+
### Behavior
2281+
2282+
- **Executes in Caller’s Context**:
2283+
- `msg.sender` and `msg.value` remain the same as in the original call that reached the caller.
2284+
- Any storage changes (writes) made by the executed code affect the caller contract’s storage layout.
2285+
- **No Ether Transfer**:
2286+
- Unlike `call`, you can’t directly send Ether with `delegatecall`. It only executes code with the current call’s context.
2287+
- **State & Storage Layout**:
2288+
- You must ensure the storage layout of the caller and the target match if the target code writes to storage. Mismatched layouts lead to corruption or undefined behavior.
2289+
- **Returns**:
2290+
- Similar to `call`, you get `(bool success, bytes memory returnData)`. You must handle them manually.
2291+
2292+
```solidity
2293+
contract Proxy {
2294+
address public implementation; // Points to logic contract
2295+
2296+
constructor(address _impl) {
2297+
implementation = _impl;
2298+
}
2299+
2300+
fallback() external payable {
2301+
// Forward all calls to 'implementation' using delegatecall
2302+
(bool success, bytes memory data) = implementation.delegatecall(msg.data);
2303+
if (!success) {
2304+
assembly {
2305+
revert(add(data, 32), mload(data))
2306+
}
2307+
}
2308+
assembly {
2309+
return(add(data, 32), mload(data))
2310+
}
2311+
}
2312+
}
2313+
```
2314+
2315+
## `staticcall`
2316+
2317+
`staticcall` is another low-level function used for read-only calls. It reverts if the called code attempts to modify state (like writing to storage or emitting events).
2318+
2319+
```solidity
2320+
(bool success, bytes memory returnData) = targetAddress.staticcall(calldata);
2321+
```
2322+
2323+
| Aspect | call | delegatecall |
2324+
| -------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
2325+
| **State Context** | Runs in the **target’s** context. Writes in the **target’s** storage. | Runs in the **caller’s** context. Writes in the **caller’s** storage. |
2326+
| **msg.sender / msg.value** | **msg.sender** is the caller (the contract executing `call`). | **msg.sender** remains the original external address or higher-level caller. |
2327+
| **Ether Transfer** | You can send Ether along with it (`value: X`). | No direct Ether transfer is possible. |
2328+
| **Common Use Case** | Directly calling external contracts, optionally sending Ether. | Proxy patterns, libraries, or upgradeable contracts (code is borrowed but state remains in the caller). |
2329+
| **Gas Forwarding** | Forwards all remaining gas unless specified otherwise. | Same, but in the caller’s context (no Ether). |
2330+
2331+
## Security & Best Practices
2332+
2333+
1. **Check Return Values**
2334+
2335+
- Both `call` and `delegatecall` return a boolean indicating success/failure. Always check it.
2336+
2337+
2. **Handle returnData**
2338+
2339+
- If the called function returns data, decode it if you care about it.
2340+
- If the call reverts with a reason string, that’s included in `returnData`. You can bubble it up using inline assembly or revert with your own error.
2341+
2342+
3. **Protect Against Reentrancy**
2343+
2344+
- If you use `call` to forward large amounts of gas, your contract can be reentered. Use patterns like **Checks-Effects-Interactions** or **ReentrancyGuard**.
2345+
2346+
4. **Proxy Storage Layout**
2347+
2348+
- With `delegatecall`, ensure the **implementation** contract’s storage layout is compatible with the proxy contract’s layout. If you add new variables in the implementation or reorder them, you can break or corrupt the proxy’s storage.
2349+
2350+
5. **Avoid Arbitrary Delegatecalls**
2351+
2352+
- Letting users choose any target for `delegatecall` is a critical security hole. Restrict target addresses or function signatures if you want safe upgrade patterns.
2353+
2354+
6. **Gas Estimation**
2355+
2356+
- Low-level calls might confuse the Solidity gas estimator. You sometimes need to manually specify gas or test thoroughly to avoid out-of-gas issues.
2357+
22142358
# Create, Create2, Create3, and CreateX
22152359

22162360
# ABI Encode & Decode
@@ -2227,7 +2371,3 @@ contract WontCompile {
22272371
- [Solidity Gas Optimization Techniques: Loops](https://hackmd.io/@totomanov/gas-optimization-loops#Solidity-Gas-Optimization-Techniques-Loops)
22282372
- [Gas Optimization In Solidity: Strategies For Cost-Effective Smart Contracts](https://hacken.io/discover/solidity-gas-optimization/)
22292373
- [Understanding the Function Selector in Solidity](https://www.rareskills.io/post/function-selector)
2230-
2231-
```
2232-
2233-
```

0 commit comments

Comments
 (0)