Skip to content

Commit f330a68

Browse files
committed
docs: add line highlights to guides
1 parent e5c1a9d commit f330a68

File tree

5 files changed

+66
-50
lines changed

5 files changed

+66
-50
lines changed

src/pages/guides/deploying-contracts.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ import {Counter} from "../src/Counter.sol";
145145
146146
contract DeployScript is Script {
147147
function run() public {
148-
vm.startBroadcast();
148+
vm.startBroadcast(); // [!code hl]
149149
150150
Counter counter = new Counter();
151151
console.log("Counter deployed at:", address(counter));
152152
153-
vm.stopBroadcast();
153+
vm.stopBroadcast(); // [!code hl]
154154
}
155155
}
156156
```

src/pages/guides/invariant-testing.mdx

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,19 @@ contract TokenHandler is Test {
141141
Token public token;
142142
143143
// Track all mints and burns
144-
uint256 public ghost_mintedSum;
145-
uint256 public ghost_burnedSum;
144+
uint256 public ghost_mintedSum; // [!code hl]
145+
uint256 public ghost_burnedSum; // [!code hl]
146146
147147
// Track per-address deltas
148-
mapping(address => int256) public ghost_balanceDeltas;
148+
mapping(address => int256) public ghost_balanceDeltas; // [!code hl]
149149
150150
function mint(address to, uint256 amount) external {
151151
amount = bound(amount, 1, 1000 ether);
152152
153153
token.mint(to, amount);
154154
155-
ghost_mintedSum += amount;
156-
ghost_balanceDeltas[to] += int256(amount);
155+
ghost_mintedSum += amount; // [!code hl]
156+
ghost_balanceDeltas[to] += int256(amount); // [!code hl]
157157
}
158158
159159
function burn(address from, uint256 amount) external {
@@ -165,16 +165,16 @@ contract TokenHandler is Test {
165165
vm.prank(from);
166166
token.burn(amount);
167167
168-
ghost_burnedSum += amount;
169-
ghost_balanceDeltas[from] -= int256(amount);
168+
ghost_burnedSum += amount; // [!code hl]
169+
ghost_balanceDeltas[from] -= int256(amount); // [!code hl]
170170
}
171171
}
172172
173173
contract TokenInvariantTest is Test {
174174
function invariant_TotalSupplyMatchesGhosts() public view {
175175
assertEq(
176176
token.totalSupply(),
177-
handler.ghost_mintedSum() - handler.ghost_burnedSum()
177+
handler.ghost_mintedSum() - handler.ghost_burnedSum() // [!code hl]
178178
);
179179
}
180180
}
@@ -201,12 +201,12 @@ function setUp() public {
201201
202202
// Only call these functions
203203
bytes4[] memory selectors = new bytes4[](2);
204-
selectors[0] = VaultHandler.deposit.selector;
205-
selectors[1] = VaultHandler.withdraw.selector;
206-
targetSelector(FuzzSelector({
207-
addr: address(handler),
208-
selectors: selectors
209-
}));
204+
selectors[0] = VaultHandler.deposit.selector; // [!code hl]
205+
selectors[1] = VaultHandler.withdraw.selector; // [!code hl]
206+
targetSelector(FuzzSelector({ // [!code hl]
207+
addr: address(handler), // [!code hl]
208+
selectors: selectors // [!code hl]
209+
})); // [!code hl]
210210
}
211211
```
212212

@@ -217,10 +217,10 @@ function setUp() public {
217217
targetContract(address(handler));
218218
219219
// Exclude specific functions
220-
excludeSelector(FuzzSelector({
221-
addr: address(handler),
222-
selectors: toSelectors(VaultHandler.debugFunction.selector)
223-
}));
220+
excludeSelector(FuzzSelector({ // [!code hl]
221+
addr: address(handler), // [!code hl]
222+
selectors: toSelectors(VaultHandler.debugFunction.selector) // [!code hl]
223+
})); // [!code hl]
224224
}
225225
226226
function toSelectors(bytes4 selector) internal pure returns (bytes4[] memory) {
@@ -237,27 +237,27 @@ Add a summary function to understand test coverage:
237237
```solidity
238238
contract VaultHandler is Test {
239239
// Call counters
240-
mapping(bytes4 => uint256) public calls;
240+
mapping(bytes4 => uint256) public calls; // [!code hl]
241241
242242
function deposit(uint256 amount) external {
243-
calls[this.deposit.selector]++;
243+
calls[this.deposit.selector]++; // [!code hl]
244244
// ...
245245
}
246246
247247
function withdraw(uint256 amount) external {
248-
calls[this.withdraw.selector]++;
248+
calls[this.withdraw.selector]++; // [!code hl]
249249
// ...
250250
}
251251
252-
function callSummary() external view {
253-
console.log("deposit calls:", calls[this.deposit.selector]);
254-
console.log("withdraw calls:", calls[this.withdraw.selector]);
255-
}
252+
function callSummary() external view { // [!code hl]
253+
console.log("deposit calls:", calls[this.deposit.selector]); // [!code hl]
254+
console.log("withdraw calls:", calls[this.withdraw.selector]); // [!code hl]
255+
} // [!code hl]
256256
}
257257
258258
contract VaultInvariantTest is Test {
259259
function invariant_CallSummary() public view {
260-
handler.callSummary();
260+
handler.callSummary(); // [!code hl]
261261
}
262262
}
263263
```

src/pages/guides/multi-chain-deployments.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,19 @@ contract DeployScript is Script {
5656
}
5757
5858
function getConfig() internal view returns (Config memory) {
59-
if (block.chainid == 1) {
59+
if (block.chainid == 1) { // [!code hl]
6060
return Config({
6161
admin: 0x1234567890123456789012345678901234567890,
6262
initialSupply: 1_000_000 ether,
6363
weth: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
6464
});
65-
} else if (block.chainid == 10) {
65+
} else if (block.chainid == 10) { // [!code hl]
6666
return Config({
6767
admin: 0x1234567890123456789012345678901234567890,
6868
initialSupply: 500_000 ether,
6969
weth: 0x4200000000000000000000000000000000000006
7070
});
71-
} else if (block.chainid == 42161) {
71+
} else if (block.chainid == 42161) { // [!code hl]
7272
return Config({
7373
admin: 0x1234567890123456789012345678901234567890,
7474
initialSupply: 500_000 ether,
@@ -178,12 +178,12 @@ contract DeployScript is Script {
178178
vm.stopBroadcast();
179179
180180
// Write deployment to JSON
181-
string memory json = vm.serializeAddress("deployment", "token", address(token));
182-
json = vm.serializeUint("deployment", "chainId", block.chainid);
183-
json = vm.serializeUint("deployment", "blockNumber", block.number);
181+
string memory json = vm.serializeAddress("deployment", "token", address(token)); // [!code hl]
182+
json = vm.serializeUint("deployment", "chainId", block.chainid); // [!code hl]
183+
json = vm.serializeUint("deployment", "blockNumber", block.number); // [!code hl]
184184
185-
string memory path = string.concat("deployments/", vm.toString(block.chainid), ".json");
186-
vm.writeJson(json, path);
185+
string memory path = string.concat("deployments/", vm.toString(block.chainid), ".json"); // [!code hl]
186+
vm.writeJson(json, path); // [!code hl]
187187
}
188188
}
189189
```

src/pages/guides/stack-too-deep.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ struct Signature {
7474
bytes32 s;
7575
}
7676
77-
function processOrder(Order calldata order, Signature calldata sig) external {
77+
function processOrder(Order calldata order, Signature calldata sig) external { // [!code hl]
7878
// Works fine
7979
}
8080
```
@@ -113,7 +113,7 @@ struct CalcVars {
113113
}
114114
115115
function calculate() external view returns (uint256) {
116-
CalcVars memory vars;
116+
CalcVars memory vars; // [!code hl]
117117
vars.a = getValue1();
118118
vars.b = getValue2();
119119
vars.c = getValue3();
@@ -202,19 +202,19 @@ Solidity supports block scoping with curly braces. Variables declared in a block
202202
function process() external {
203203
uint256 result;
204204
205-
{
205+
{ // [!code hl]
206206
uint256 temp1 = getValue1();
207207
uint256 temp2 = getValue2();
208208
result = temp1 + temp2;
209209
// temp1 and temp2 go out of scope here
210-
}
210+
} // [!code hl]
211211
212-
{
212+
{ // [!code hl]
213213
uint256 temp3 = getValue3();
214214
uint256 temp4 = getValue4();
215215
result += temp3 * temp4;
216216
// temp3 and temp4 go out of scope here
217-
}
217+
} // [!code hl]
218218
}
219219
```
220220

src/pages/guides/upgrading-contracts.mdx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ This guide covers deploying and upgrading proxy contracts with Forge, including
1616

1717
### UUPS proxy deployment
1818

19+
In the UUPS pattern, the proxy is a thin contract that delegates all calls to an implementation contract. The implementation holds both the business logic and the upgrade logic. Because the proxy uses `delegatecall`, all state is stored on the proxy - the implementation is just code.
20+
1921
::::steps
2022

2123
#### Create the implementation
2224

25+
Upgradeable contracts replace constructors with `initialize` functions, since constructor logic runs in the implementation's context, not the proxy's. `_disableInitializers()` in the constructor prevents anyone from calling `initialize` directly on the implementation, which could let an attacker take ownership of it.
26+
2327
```solidity [src/TokenV1.sol]
2428
// SPDX-License-Identifier: MIT
2529
pragma solidity ^0.8.13;
@@ -52,6 +56,8 @@ contract TokenV1 is UUPSUpgradeable, OwnableUpgradeable {
5256

5357
#### Deploy the proxy
5458

59+
Deployment is a two-step process: deploy the implementation, then deploy an ERC1967 proxy pointing to it. The proxy constructor accepts initialization calldata, so the `initialize` function is called atomically during deployment.
60+
5561
```solidity [script/DeployToken.s.sol]
5662
// SPDX-License-Identifier: MIT
5763
pragma solidity ^0.8.13;
@@ -87,6 +93,8 @@ contract DeployToken is Script {
8793

8894
#### Create the new implementation
8995

96+
The new implementation inherits from V1 and adds state variables **at the end** of the storage layout. Inserting or reordering variables would corrupt existing proxy state.
97+
9098
```solidity [src/TokenV2.sol]
9199
// SPDX-License-Identifier: MIT
92100
pragma solidity ^0.8.13;
@@ -95,7 +103,7 @@ import {TokenV1} from "./TokenV1.sol";
95103
96104
contract TokenV2 is TokenV1 {
97105
// New state variables must be added at the end
98-
mapping(address => bool) public frozen;
106+
mapping(address => bool) public frozen; // [!code ++]
99107
100108
function freeze(address account) external onlyOwner {
101109
frozen[account] = true;
@@ -109,6 +117,8 @@ contract TokenV2 is TokenV1 {
109117

110118
#### Deploy and upgrade
111119

120+
To upgrade, deploy the new implementation and call `upgradeToAndCall` on the proxy. This updates the implementation address stored in the proxy's ERC1967 slot. The second argument is optional calldata for a migration function - pass empty bytes if no re-initialization is needed.
121+
112122
```solidity [script/UpgradeToken.s.sol]
113123
// SPDX-License-Identifier: MIT
114124
pragma solidity ^0.8.13;
@@ -145,7 +155,7 @@ contract UpgradeToken is Script {
145155

146156
### Storage layout verification
147157

148-
Foundry can check storage layout compatibility between implementations:
158+
Since the proxy stores all state, the new implementation's storage layout must be compatible with the old one. Foundry can export and diff storage layouts to catch breaking changes before deployment:
149159

150160
```bash
151161
$ forge inspect src/TokenV1.sol:TokenV1 storage-layout --pretty > v1-layout.txt
@@ -165,26 +175,30 @@ $ diff v1-layout.txt v2-layout.txt
165175

166176
#### Using storage gaps
167177

178+
Storage gaps are fixed-size arrays that reserve empty storage slots in a base contract. When you add new state variables in an upgrade, you reduce the gap size by the same number of slots, keeping the total storage layout unchanged and preventing slot collisions with inherited contracts.
179+
168180
```solidity
169181
contract TokenV1 is UUPSUpgradeable, OwnableUpgradeable {
170182
mapping(address => uint256) public balances;
171183
uint256 public totalSupply;
172184
173185
// Reserve 50 slots for future variables
174-
uint256[50] private __gap;
186+
uint256[50] private __gap; // [!code hl]
175187
}
176188
177189
contract TokenV2 is TokenV1 {
178190
// Uses one slot from the gap
179-
mapping(address => bool) public frozen;
191+
mapping(address => bool) public frozen; // [!code hl]
180192
181193
// Update gap to maintain total slot count
182-
uint256[49] private __gap;
194+
uint256[49] private __gap; // [!code hl]
183195
}
184196
```
185197

186198
### Transparent proxy deployment
187199

200+
In the transparent proxy pattern, the proxy itself contains the upgrade logic and a separate admin address. Only the admin can call upgrade functions; all other callers are transparently delegated to the implementation. This avoids function selector clashes between the proxy and implementation but costs more gas per call due to the admin check.
201+
188202
```solidity [script/DeployTransparent.s.sol]
189203
// SPDX-License-Identifier: MIT
190204
pragma solidity ^0.8.13;
@@ -221,6 +235,8 @@ contract DeployTransparent is Script {
221235

222236
### Testing upgrades
223237

238+
Upgrade tests should verify three things: state is preserved across the upgrade, new functionality works, and only authorized accounts can trigger upgrades.
239+
224240
```solidity [test/TokenUpgrade.t.sol]
225241
// SPDX-License-Identifier: MIT
226242
pragma solidity ^0.8.13;
@@ -242,16 +258,16 @@ contract TokenUpgradeTest is Test {
242258
243259
function test_UpgradePreservesState() public {
244260
// Set state in V1
245-
proxy.mint(address(0x1), 1000);
261+
proxy.mint(address(0x1), 1000); // [!code hl]
246262
assertEq(proxy.balances(address(0x1)), 1000);
247263
248264
// Upgrade to V2
249265
TokenV2 newImpl = new TokenV2();
250-
proxy.upgradeToAndCall(address(newImpl), "");
266+
proxy.upgradeToAndCall(address(newImpl), ""); // [!code hl]
251267
252268
// State is preserved
253269
TokenV2 proxyV2 = TokenV2(address(proxy));
254-
assertEq(proxyV2.balances(address(0x1)), 1000);
270+
assertEq(proxyV2.balances(address(0x1)), 1000); // [!code hl]
255271
assertEq(proxyV2.version(), 2);
256272
}
257273

0 commit comments

Comments
 (0)