# Cheatcodes Foundry Que Vous N'utilisez Probablement Pas
Tout le monde connaît ``vm.prank`, `vm.expectRevert`, et `vm.deal`. Mais Foundry a des dizaines d'autres cheatcodes ultra-puissants que personne n'utilise.
Voici 10 cheatcodes sous-estimés qui vont changer votre workflow de test.
1. `vm.assume` — Fuzzing Contraint
Le fuzzing Foundry génère des inputs aléatoires. Mais parfois vous voulez contraindre ces inputs.
// ❌ Mauvaise approche : skip avec require
function testTransfer(uint256 amount) public {
require(amount > 0 && amount <= 1e18); // Skip si faux
token.transfer(alice, amount);
}
// ✅ Bonne approche : vm.assume
function testTransfer(uint256 amount) public {
vm.assume(amount > 0 && amount <= 1e18); // Foundry génère seulement des inputs valides
token.transfer(alice, amount);
}
Différence :
- `
require`→ Foundry gaspille des runs sur des inputs invalides
- `
vm.assume`→ Foundry génère directement des inputs valides
Astuce : ne pas mettre de contraintes trop strictes (sinon Foundry met trop de temps à trouver des inputs).
// ❌ Trop strict (1 sur 1M inputs valides)
vm.assume(amount == 123456);
// ✅ Range raisonnable
vm.assume(amount > 1e6 && amount < 1e18);
2. `vm.store` — Manipuler le Storage Directement
Vous voulez forcer une valeur dans le storage d'un contrat sans passer par ses fonctions.
// Contrat cible
contract Vault {
mapping(address => uint256) public balances; // Slot 0
}
// Test
function testStorageManipulation() public {
Vault vault = new Vault();
// Calculer le slot du mapping
bytes32 slot = keccak256(abi.encode(alice, 0));
// Forcer balances[alice] = 1000e18
vm.store(address(vault), slot, bytes32(uint256(1000e18)));
assertEq(vault.balances(alice), 1000e18); // ✅ Pass
}
Pourquoi c'est utile :
- Tester des états edge-case sans setup complexe
- Simuler des bugs (ex: balance corrompue)
- Forking : modifier l'état d'un contrat mainnet
Astuce : utilisez `forge inspect ` pour trouver les slots.
3. `vm.mockCall` — Mock des External Calls
Vous voulez tester votre contrat sans dépendre d'un oracle/contrat externe.
// Votre contrat
contract PriceConsumer {
IOracle public oracle;
function getPrice() external view returns (uint256) {
return oracle.latestPrice();
}
}
// Test avec mock
function testPriceWithMock() public {
PriceConsumer consumer = new PriceConsumer();
// Mock l'appel à oracle.latestPrice() → retourne 3000e8
vm.mockCall(
address(consumer.oracle()),
abi.encodeWithSelector(IOracle.latestPrice.selector),
abi.encode(3000e8) // Return value
);
assertEq(consumer.getPrice(), 3000e8); // ✅ Utilise le mock
}
Pourquoi c'est utile :
- Tester sans déployer l'oracle
- Simuler des cas extrêmes (prix à 0, overflow, etc.)
- Tests plus rapides (pas de setup lourd)
Attention : `vm.mockCall` persiste jusqu'à `vm.clearMockedCalls()`.
4. `vm.expectEmit` — Vérifier les Events (Correctement)
Vérifier qu'un event est émis avec les bons paramètres.
event Transfer(address indexed from, address indexed to, uint256 amount);
function testTransferEvent() public {
// Déclare qu'on attend cet event
vm.expectEmit(true, true, false, true);
emit Transfer(alice, bob, 100e18); // Event attendu
// Action qui doit émettre l'event
token.transfer(bob, 100e18);
}
Paramètres de `vm.expectEmit` :
vm.expectEmit(
checkTopic1, // Vérifier from
checkTopic2, // Vérifier to
checkTopic3, // Vérifier (pas utilisé ici)
checkData // Vérifier amount
)
Cas d'usage avancé : vérifier plusieurs events dans l'ordre.
function testMultipleEvents() public {
vm.expectEmit(true, true, false, true);
emit Approval(alice, bob, 100e18);
vm.expectEmit(true, true, false, true);
emit Transfer(alice, bob, 100e18);
token.approve(bob, 100e18);
token.transferFrom(alice, bob, 100e18);
}
5. `vm.recordLogs` + `vm.getRecordedLogs` — Parser les Events
Vous voulez récupérer les events émis et les parser.
function testRecordLogs() public {
vm.recordLogs(); // Start recording
token.transfer(bob, 100e18);
token.transfer(charlie, 50e18);
Vm.Log[] memory logs = vm.getRecordedLogs();
// Parser les events
assertEq(logs.length, 2);
assertEq(logs[0].topics[0], keccak256("Transfer(address,address,uint256)"));
// Décoder les données
(address from, address to, uint256 amount) = abi.decode(
logs[0].data,
(address, address, uint256)
);
assertEq(amount, 100e18);
}
Pourquoi c'est utile :
- Tester des contrats complexes qui émettent beaucoup d'events
- Vérifier l'ordre des events
- Debug : voir exactement ce qui a été émis
6. `vm.snapshot` + `vm.revertTo` — Time Travel
Créer un snapshot de l'état de la blockchain et y revenir plus tard.
function testSnapshot() public {
token.mint(alice, 100e18);
assertEq(token.balanceOf(alice), 100e18);
// Snapshot
uint256 snapshot = vm.snapshot();
// Modifications
token.transfer(bob, 50e18);
assertEq(token.balanceOf(alice), 50e18);
// Revenir au snapshot
vm.revertTo(snapshot);
// État restauré
assertEq(token.balanceOf(alice), 100e18);
assertEq(token.balanceOf(bob), 0);
}
Pourquoi c'est utile :
- Tester plusieurs scénarios à partir du même état initial
- Éviter de re-déployer les contrats entre tests
- Tests plus rapides
7. `vm.warp` + `vm.roll` — Voyager dans le Temps
Modifier `block.timestamp` et `block.number`.
function testVesting() public {
uint256 start = block.timestamp;
vesting.start();
// Avancer de 30 jours
vm.warp(start + 30 days);
// Vérifier les tokens débloqués
uint256 unlocked = vesting.unlockedAmount(alice);
assertEq(unlocked, 100e18); // 30 jours → 100 tokens
// Avancer de 1000 blocs
vm.roll(block.number + 1000);
}
Attention : `vm.warp` ne modifie pas `block.number`. Utilisez `vm.roll` si votre contrat dépend du numéro de bloc.
8. `vm.chainId` — Tester le Multi-Chain
Changer le `block.chainid` dynamiquement.
contract MultiChainContract {
function getChainName() external view returns (string memory) {
if (block.chainid == 1) return "Ethereum";
if (block.chainid == 137) return "Polygon";
if (block.chainid == 42161) return "Arbitrum";
revert("Unknown chain");
}
}
function testMultiChain() public {
MultiChainContract c = new MultiChainContract();
vm.chainId(1);
assertEq(c.getChainName(), "Ethereum");
vm.chainId(137);
assertEq(c.getChainName(), "Polygon");
vm.chainId(42161);
assertEq(c.getChainName(), "Arbitrum");
}
Pourquoi c'est utile : tester des contrats multi-chain sans forker plusieurs networks.
9. `vm.signCompact` — Générer des Signatures EIP-2098
Signer des messages avec des compact signatures (64 bytes au lieu de 65).
function testCompactSignature() public {
uint256 privateKey = 0xabc123;
address signer = vm.addr(privateKey);
bytes32 messageHash = keccak256("Hello World");
// Signature compacte (64 bytes)
(bytes32 r, bytes32 vs) = vm.signCompact(privateKey, messageHash);
// Vérifier
address recovered = ecrecover(
messageHash,
uint8((uint256(vs) >> 255) + 27), // v
r,
bytes32(uint256(vs) & ((1 << 255) - 1)) // s
);
assertEq(recovered, signer);
}
Pourquoi c'est utile : signatures plus compactes (économie de gas pour les calldata).
10. `vm.createSelectFork` — Fork à la Volée
Créer un fork d'une chain à un block spécifique.
function testForkMainnet() public {
// Fork mainnet au block 20000000
uint256 mainnetFork = vm.createSelectFork("https://eth.llamarpc.com", 20000000);
// Interact avec des contrats mainnet
IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
uint256 balance = usdc.balanceOf(0x123...);
// Switch vers un autre fork
uint256 arbitrumFork = vm.createSelectFork("https://arb1.arbitrum.io/rpc", 150000000);
vm.selectFork(arbitrumFork);
// Maintenant on est sur Arbitrum
}
Pourquoi c'est utile :
- Tester des intégrations avec des contrats mainnet
- Simuler des scénarios réels (ex: liquidation Aave)
- Multi-fork : tester cross-chain dans un seul test
Bonus : Combiner les Cheatcodes
Les cheatcodes sont composables.
function testComplexScenario() public {
// Fork mainnet
vm.createSelectFork("https://eth.llamarpc.com");
// Manipuler le storage d'un contrat mainnet
address vault = 0x123...;
bytes32 slot = keccak256(abi.encode(alice, 0));
vm.store(vault, slot, bytes32(uint256(1000e18)));
// Mock un oracle
vm.mockCall(
oracleAddress,
abi.encodeWithSelector(IOracle.getPrice.selector),
abi.encode(3000e8)
);
// Time travel
vm.warp(block.timestamp + 7 days);
// Vérifier les events
vm.expectEmit(true, true, false, true);
emit Withdrawal(alice, 1000e18);
// Action
vm.prank(alice);
IVault(vault).withdraw(1000e18);
}
Conclusion
Ces 10 cheatcodes transforment Foundry en outil de test ultra-puissant :
vm.assume` → fuzzing contraintvm.store` → manipuler le storagevm.mockCall` → mock des external callsvm.expectEmit` → vérifier les eventsvm.recordLogs` → parser les eventsvm.snapshot` → snapshots d'étatvm.warp` + `vm.roll` → time travelvm.chainId` → multi-chainvm.signCompact` → signatures compactesvm.createSelectFork` → forking dynamiquePro tip : lisez la doc complète des cheatcodes (`forge doc``) — il y en a 50+ autres.
Testez mieux. 🧪