Tutorial·8 min का पठन·Solingo द्वारा

Foundry Cheatcodes जो आप शायद use नहीं कर रहे

vm.prank से आगे। Fuzzing, forking, और real-world scenarios के लिए powerful cheatcodes।

# Foundry Cheatcodes जो आप शायद use नहीं कर रहे

हर कोई vm.prank और vm.expectRevert जानता है। लेकिन Foundry में 100+ cheatcodes हैं। यहाँ सबसे underrated और powerful ones।

1. vm.assume — Fuzzing के लिए

Fuzzing tests में invalid inputs को skip करना common है। vm.assume इसे clean बनाता है।

Without vm.assume

function testTransfer(address to, uint amount) public {

if (to == address(0)) return; // Skip

if (amount == 0) return; // Skip

if (amount > token.balanceOf(user)) return; // Skip

vm.prank(user);

token.transfer(to, amount);

assertEq(token.balanceOf(to), amount);

}

Problem: Early returns waste fuzzing runs। Foundry इन्हें "successful" count करता है।

With vm.assume

function testTransfer(address to, uint amount) public {

vm.assume(to != address(0));

vm.assume(amount > 0);

vm.assume(amount <= token.balanceOf(user));

vm.prank(user);

token.transfer(to, amount);

assertEq(token.balanceOf(to), amount);

}

Benefit: Foundry automatically invalid inputs reject करता है। Real test cases ज्यादा चलते हैं।

Pro Tip: Combine Assumptions

vm.assume(

to != address(0) &&

amount > 0 &&

amount <= 1e30 // Reasonable upper bound

);

---

2. vm.store — Direct Storage Manipulation

किसी भी contract का storage directly modify करो। Incredibly powerful initialization और edge case testing के लिए।

Example: Set Uniswap Pool Reserves

function testFlashLoan() public {

IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);

// Directly set reserves to 1M USDC, 500 ETH

vm.store(

address(pair),

bytes32(uint(8)), // Slot 8 = reserve0

bytes32(uint(1_000_000e6))

);

vm.store(

address(pair),

bytes32(uint(9)), // Slot 9 = reserve1

bytes32(uint(500 ether))

);

// Now test flash loan with known reserves

}

Finding Storage Slots

forge inspect MyContract storage-layout

Mapping Storage Slots

Mappings का slot calculate करना:

// mapping(address => uint) public balances; at slot 5

address user = 0x123...;

bytes32 slot = keccak256(abi.encode(user, uint(5)));

vm.store(tokenAddress, slot, bytes32(uint(1000 ether)));

// User now has 1000 tokens!

---

3. vm.mockCall — External Dependencies Mock करें

Third-party contracts mock करो without deploying them।

function testPriceDependent() public {

address oracle = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;

// Mock latestRoundData() to return $2000 ETH price

vm.mockCall(

oracle,

abi.encodeWithSelector(

AggregatorV3Interface.latestRoundData.selector

),

abi.encode(

uint80(0), // roundId

int256(2000e8), // answer

uint256(0), // startedAt

block.timestamp, // updatedAt

uint80(0) // answeredInRound

)

);

// Now vault.getETHPrice() returns 2000

vault.deposit{value: 1 ether}();

assertEq(vault.collateralValue(address(this)), 2000e18);

}

Mock Multiple Calls

// First call returns 2000, second returns 1800

vm.mockCall(oracle, selector, abi.encode(2000e8));

uint price1 = vault.getPrice(); // 2000

vm.mockCall(oracle, selector, abi.encode(1800e8));

uint price2 = vault.getPrice(); // 1800

// Test liquidation on price drop

---

4. vm.expectEmit — Event Testing Done Right

Events verify करना tricky है। vm.expectEmit इसे simple बनाता है।

Basic Usage

function testDeposit() public {

// Expect Transfer event with these parameters

vm.expectEmit(true, true, false, true);

emit Transfer(address(0), user, 100e18);

vault.deposit{value: 1 ether}();

}

Parameters Explained

vm.expectEmit(

checkTopic1, // Check first indexed param?

checkTopic2, // Check second indexed param?

checkTopic3, // Check third indexed param?

checkData // Check non-indexed params?

);

Example: Partial Matching

event Swap(

address indexed from,

address indexed to,

uint amountIn,

uint amountOut // Don't care about exact amount

);

// Check from/to, ignore amounts

vm.expectEmit(true, true, false, false);

emit Swap(user, address(vault), 0, 0);

router.swap(tokenA, tokenB, 100e18);

---

5. vm.recordLogs — Capture All Events

सभी events capture करो और programmatically analyze करो।

function testBatchTransfer() public {

vm.recordLogs();

vault.batchTransfer([user1, user2, user3], [10, 20, 30]);

Vm.Log[] memory logs = vm.getRecordedLogs();

uint transferCount = 0;

for (uint i = 0; i < logs.length; i++) {

if (logs[i].topics[0] == keccak256("Transfer(address,address,uint256)")) {

transferCount++;

}

}

assertEq(transferCount, 3, "Should emit 3 Transfer events");

}

Decode Event Data

for (uint i = 0; i < logs.length; i++) {

if (logs[i].topics[0] == keccak256("Transfer(address,address,uint256)")) {

address from = address(uint160(uint(logs[i].topics[1])));

address to = address(uint160(uint(logs[i].topics[2])));

uint amount = abi.decode(logs[i].data, (uint));

console.log("Transfer:", from, to, amount);

}

}

---

6. vm.snapshot + vm.revertTo — State Management

Complex test setups को snapshot करो और reuse करो।

contract VaultTest is Test {

uint snapshotId;

function setUp() public {

// Deploy contracts

vault = new Vault();

token.mint(user, 1000e18);

// Save initial state

snapshotId = vm.snapshot();

}

function testScenarioA() public {

vault.deposit(100e18);

// ... test logic

// Revert to clean state

vm.revertTo(snapshotId);

}

function testScenarioB() public {

// Starts from clean snapshot

vault.deposit(200e18);

// ... different test

}

}

Benefit: No need to redeploy contracts। Tests 3x faster! ⚡

---

7. vm.warp + vm.roll — Time Travel

Time और block number manipulate करो।

vm.warp — Set block.timestamp

function testVesting() public {

vault.startVesting(user, 1000e18, 365 days);

// Jump 6 months ahead

vm.warp(block.timestamp + 180 days);

uint claimable = vault.claimable(user);

assertApproxEqRel(claimable, 500e18, 0.01e18); // ~50%

// Jump to end

vm.warp(block.timestamp + 185 days);

assertEq(vault.claimable(user), 1000e18); // 100%

}

vm.roll — Set block.number

function testBlockDelay() public {

vault.propose(proposalId);

// Need 100 blocks to elapse

vm.roll(block.number + 100);

vault.execute(proposalId); // Now allowed

}

---

8. vm.createSelectFork — Fork Testing

Multiple forks के बीच switch करो।

contract CrossChainTest is Test {

uint mainnetFork;

uint arbitrumFork;

function setUp() public {

mainnetFork = vm.createFork("mainnet");

arbitrumFork = vm.createFork("arbitrum");

}

function testBridge() public {

// Start on mainnet

vm.selectFork(mainnetFork);

bridge.deposit{value: 1 ether}(user);

// Switch to Arbitrum

vm.selectFork(arbitrumFork);

bridge.claim(user);

assertEq(user.balance, 1 ether);

}

}

Pro Tip: Fork at Specific Block

// Fork at block where hack happened

uint forkId = vm.createFork("mainnet", 15_000_000);

vm.selectFork(forkId);

// Replay attack and test fix

---

9. vm.chainId — Multi-Chain Testing

Chain ID change करो — EIP-712 signatures test करने के लिए useful।

function testSignatureReplay() public {

// Sign on Ethereum mainnet (chainId 1)

vm.chainId(1);

bytes32 digest = vault.getDigest(user, 100e18);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, digest);

// Try to replay on Polygon (chainId 137)

vm.chainId(137);

vm.expectRevert("Invalid signature");

vault.withdrawWithSignature(user, 100e18, v, r, s);

}

---

10. vm.signCompact — Compact Signatures

EIP-2098 compact signatures (64 bytes instead of 65)।

function testCompactSig() public {

bytes32 digest = keccak256("message");

// Standard signature: 65 bytes

(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);

// Compact signature: 64 bytes

(bytes32 r2, bytes32 vs) = vm.signCompact(privateKey, digest);

// vs encodes both s and v

// Saves 32 bytes in calldata (cheaper!)

}

Use Case: Batch Signatures

// Standard: 10 signatures = 650 bytes

// Compact: 10 signatures = 640 bytes (10 bytes saved)

function batchVerify(bytes32[] memory r, bytes32[] memory vs) external {

for (uint i = 0; i < r.length; i++) {

address signer = recoverCompact(digest, r[i], vs[i]);

// ...

}

}

---

Bonus: Combine Cheatcodes

Real power तब आती है जब आप इन्हें combine करते हैं:

function testComplexScenario() public {

// 1. Fork mainnet at specific block

uint forkId = vm.createFork("mainnet", 18_000_000);

vm.selectFork(forkId);

// 2. Mock Chainlink price

vm.mockCall(oracle, selector, abi.encode(2000e8));

// 3. Manipulate storage to give user tokens

bytes32 slot = keccak256(abi.encode(user, 0));

vm.store(tokenAddress, slot, bytes32(uint(1000e18)));

// 4. Record events

vm.recordLogs();

// 5. Warp time forward

vm.warp(block.timestamp + 30 days);

// 6. Execute as user

vm.prank(user);

vault.compound();

// 7. Verify events

Vm.Log[] memory logs = vm.getRecordedLogs();

assertGt(logs.length, 0);

}

---

Best Practices

  • vm.assume > early returns: Fuzzing efficiency के लिए
  • vm.snapshot tests fast रखता है: Redeploy मत करो
  • vm.mockCall > deploy fake contracts: Clean और fast
  • vm.expectEmit events verify करो: Critical business logic
  • vm.store carefully use करो: Storage layout समझना जरूरी
  • Conclusion

    Foundry's cheatcodes incredibly powerful हैं। vm.prank से आगे देखो — testing superpowers unlock करो।

    Practice करो:

    • Fuzzing में vm.assume use करो
    • Complex setups vm.snapshot से optimize करो
    • External dependencies vm.mockCall से mock करो
    • Events vm.expectEmit से verify करो

    Happy testing! 🧪

    Practice में लगाने के लिए तैयार हैं?

    Solingo पर interactive exercises के साथ इन concepts को apply करें।

    मुफ्त में शुरू करें