Tutoriel·8 min de lecture·Par Solingo

Cheatcodes Foundry Que Vous N'utilisez Probablement Pas

Au-delà de vm.prank. Cheatcodes puissants pour fuzzing, forking, et simulation de scénarios réels.

# 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 storage-layout` 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 contraint
  • `vm.store` → manipuler le storage
  • `vm.mockCall` → mock des external calls
  • `vm.expectEmit` → vérifier les events
  • `vm.recordLogs` → parser les events
  • `vm.snapshot` → snapshots d'état
  • `vm.warp` + `vm.roll` → time travel
  • `vm.chainId` → multi-chain
  • `vm.signCompact` → signatures compactes
  • `vm.createSelectFork` → forking dynamique
  • Pro tip : lisez la doc complète des cheatcodes (`forge doc``) — il y en a 50+ autres.

    Testez mieux. 🧪

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement