मुख्य सामग्री पर जाएं
यह ट्यूटोरियल Foundry में एक modular cheatcode playground बनाता है। आप एक ही contract को Virtual Environment पर deploy करते हैं, फिर transactions के अंदर से हर Solidity precompile cheatcode का अभ्यास करने के लिए इसके functions को cast send के साथ कॉल करते हैं।

आप क्या बनाएंगे

तीन files:
  • src/IVnetPrecompile.sol: precompile interface।
  • src/MyTestSetup.sol: एक playground contract। प्रत्येक function एक precompile call को wrap करता है, इसलिए आप एक बार deploy करते हैं और एक बार में state के एक टुकड़े को manipulate करने के लिए individual functions को कॉल करते हैं।
  • script/Deploy.s.sol: एक forge script deploy।

Function map

श्रेणीFunctionWraps
BalanceforceBalance(account, newBalance)setBalance
topUp(account, amount)addBalance
NonceforceNonce(account, newNonce)setNonce
StoragewriteStorage(target, slot, value)setStorageAt
readStorage(target, slot) → bytes32getStorageAt
EventsfireTransfer(token, from, to, amount)emitEvent (ERC-20 Transfer)
fireEvent(target, topics[], data)emitEvent (generic, 0–4 topics)
DemodemoScenario(account)balance, nonce, storage, USDC Transfer को जोड़ता है

पूर्वापेक्षाएँ

  • एक project के साथ Tenderly account।
  • Foundry स्थापित (curl -L https://foundry.paradigm.xyz | bash && foundryup)।
1
Virtual Environment बनाएं
2
Tenderly Dashboard में, Virtual Environments पर जाएं और एक Virtual Environment बनाएं। Admin RPC URL कॉपी करें, public RPC funding और verification calls पर 401 लौटाता है, इसलिए इस ट्यूटोरियल में हमेशा Admin RPC का उपयोग करें।
3
Foundry project को initialize करें
4
forge init tenderly-cheatcodes
cd tenderly-cheatcodes
5
precompile interface जोड़ें
6
src/IVnetPrecompile.sol के रूप में save करें:
7
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVnetPrecompile {
    function setStorageAt(address target, bytes32 slot, bytes32 value) external returns (bool);
    function getStorageAt(address target, bytes32 slot) external view returns (bytes32);
    function setBalance(address target, uint256 balance) external returns (bool);
    function addBalance(address target, uint256 amount) external returns (bool);
    function setNonce(address target, uint256 value) external returns (bool);

    function emitEvent(address target, bytes calldata data) external returns (bool);
    function emitEvent(address target, bytes32 topic1) external returns (bool);
    function emitEvent(address target, bytes32 topic1, bytes calldata data) external returns (bool);
    function emitEvent(address target, bytes32 topic1, bytes32 topic2) external returns (bool);
    function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes calldata data) external returns (bool);
    function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3) external returns (bool);
    function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes calldata data) external returns (bool);
    function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes32 topic4) external returns (bool);
    function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes32 topic4, bytes calldata data) external returns (bool);
}
8
playground contract जोड़ें
9
src/MyTestSetup.sol के रूप में save करें:
10
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IVnetPrecompile} from "./IVnetPrecompile.sol";

contract MyTestSetup {
    IVnetPrecompile constant VNET = IVnetPrecompile(0xc100000000000000000000000000000000000000);

    bytes32 constant TRANSFER_TOPIC = keccak256("Transfer(address,address,uint256)");

    function forceBalance(address account, uint256 amount) external {
        VNET.setBalance(account, amount);
    }

    function topUp(address account, uint256 amount) external {
        VNET.addBalance(account, amount);
    }

    function forceNonce(address account, uint256 n) external {
        VNET.setNonce(account, n);
    }

    function writeStorage(address target, bytes32 slot, bytes32 value) external {
        VNET.setStorageAt(target, slot, value);
    }

    function readStorage(address target, bytes32 slot) external view returns (bytes32) {
        return VNET.getStorageAt(target, slot);
    }

    function fireTransfer(address token, address from, address to, uint256 amount) external {
        VNET.emitEvent(
            token,
            TRANSFER_TOPIC,
            bytes32(uint256(uint160(from))),
            bytes32(uint256(uint160(to))),
            abi.encode(amount)
        );
    }

    function fireEvent(address target, bytes32[] calldata topics, bytes calldata data) external {
        if (topics.length == 0)      VNET.emitEvent(target, data);
        else if (topics.length == 1) VNET.emitEvent(target, topics[0], data);
        else if (topics.length == 2) VNET.emitEvent(target, topics[0], topics[1], data);
        else if (topics.length == 3) VNET.emitEvent(target, topics[0], topics[1], topics[2], data);
        else                         VNET.emitEvent(target, topics[0], topics[1], topics[2], topics[3], data);
    }

    function demoScenario(address account) external {
        VNET.setBalance(account, 10 ether);
        VNET.setNonce(account, 5);
        VNET.setStorageAt(account, bytes32(uint256(0)), bytes32(uint256(0x1234)));
        VNET.addBalance(account, 2 ether);
        VNET.emitEvent(
            0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48,
            TRANSFER_TOPIC,
            bytes32(uint256(0)),
            bytes32(uint256(uint160(account))),
            abi.encode(uint256(1000e6))
        );
    }
}
11
deploy script जोड़ें
12
script/Deploy.s.sol के रूप में save करें:
13
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Script} from "forge-std/Script.sol";
import {MyTestSetup} from "../src/MyTestSetup.sol";

contract Deploy is Script {
    function run() external {
        vm.startBroadcast();
        new MyTestSetup();
        vm.stopBroadcast();
    }
}
14
environment variables कॉन्फ़िगर करें
15
.env के रूप में save करें:
16
VNET_RPC=https://virtual.mainnet.eu.rpc.tenderly.co/<your-virtual-env-uuid>
PRIVATE_KEY=0x...
DEPLOYER=0x...
17
इसे source करें: source .env
18
deployer को fund करें
19
cast rpc tenderly_setBalance "[\"$DEPLOYER\"]" "0x56BC75E2D63100000" --rpc-url "$VNET_RPC"
20
Deploy और verify
21
forge script script/Deploy.s.sol:Deploy \
    --rpc-url "$VNET_RPC" \
    --private-key "$PRIVATE_KEY" \
    --broadcast --slow \
    --verify \
    --verifier custom \
    --verifier-url "$VNET_RPC/verify"
22
script output से deployed address को export करें:
23
export TEST_CONTRACT=0x...
24
cheatcodes का अभ्यास करें
25
प्रत्येक call एक अलग transaction है। किसी भी समय अलग arguments के साथ फिर से चलाएं, कोई redeployment आवश्यक नहीं।
26
# Set 999 ETH on an arbitrary account
cast send "$TEST_CONTRACT" "forceBalance(address,uint256)" \
    0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 999000000000000000000 \
    --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

# Set vitalik.eth's nonce to 4242
cast send "$TEST_CONTRACT" "forceNonce(address,uint256)" \
    0xd8da6bf26964af9d7eed9e03e53415d37aa96045 4242 \
    --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

# Write storage slot 0 of a contract
cast send "$TEST_CONTRACT" "writeStorage(address,bytes32,bytes32)" \
    "$TARGET" \
    0x0000000000000000000000000000000000000000000000000000000000000000 \
    0x0000000000000000000000000000000000000000000000000000000000001234 \
    --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

# Spoof a 1 DAI Transfer from the real DAI contract
cast send "$TEST_CONTRACT" "fireTransfer(address,address,address,uint256)" \
    0x6B175474E89094C44Da98b954EedeAC495271d0F \
    0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC \
    0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD \
    1000000000000000000 \
    --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"

# Spoof a BAYC ERC-721 Transfer of token #1234 (all args indexed, data is empty)
cast send "$TEST_CONTRACT" "fireEvent(address,bytes32[],bytes)" \
    0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D \
    '[0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef,0x000000000000000000000000CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC,0x000000000000000000000000DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD,0x00000000000000000000000000000000000000000000000000000000000004d2]' \
    0x \
    --rpc-url "$VNET_RPC" --private-key "$PRIVATE_KEY"
27
state changes को verify करें
28
cast balance 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA --rpc-url "$VNET_RPC"
cast nonce   0xd8da6bf26964af9d7eed9e03e53415d37aa96045 --rpc-url "$VNET_RPC"
cast storage "$TARGET" 0                                --rpc-url "$VNET_RPC"
29
spoofed log की पुष्टि करें
30
receipt खींचें और logs[] में एक entry जाँचें जिसका address उस contract से match करता है जिसे आपने spoof किया है:
31
cast receipt <TX_HASH> --rpc-url "$VNET_RPC" --json | jq '.logs'
32
BAYC उदाहरण के लिए, receipt में शामिल होना चाहिए:
33
{
  "address": "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d",
  "topics": [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    "0x000000000000000000000000cccccccccccccccccccccccccccccccccccccccc",
    "0x000000000000000000000000dddddddddddddddddddddddddddddddddddddddd",
    "0x00000000000000000000000000000000000000000000000000000000000004d2"
  ],
  "data": "0x"
}
34
address BAYC contract से match करता है, topics[0] Transfer event signature hash है, और topics[1–3] indexed from, to, और tokenId को वहन करते हैं। BAYC transfers के लिए listen कर रहा एक indexer इस log को एक वास्तविक emission की तरह प्रोसेस करेगा।
35
precompile address = 0xc100000000000000000000000000000000000000 के साथ diagnostic logs भी उत्सर्जित करता है, प्रत्येक state mutation रिकॉर्ड करता है। precompile की अपनी entries से spoofed log को अलग करने के लिए logs[] को target address द्वारा filter करें।

यह भी देखें

  • Solidity Cheatcodes reference, पूर्ण interface परिभाषा और function table।
  • Admin RPC, JSON-RPC के माध्यम से transaction के बाहर से state manipulation।