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 scriptdeploy।
Function map
| श्रेणी | Function | Wraps |
|---|---|---|
| Balance | forceBalance(account, newBalance) | setBalance |
topUp(account, amount) | addBalance | |
| Nonce | forceNonce(account, newNonce) | setNonce |
| Storage | writeStorage(target, slot, value) | setStorageAt |
readStorage(target, slot) → bytes32 | getStorageAt | |
| Events | fireTransfer(token, from, to, amount) | emitEvent (ERC-20 Transfer) |
fireEvent(target, topics[], data) | emitEvent (generic, 0–4 topics) | |
| Demo | demoScenario(account) | balance, nonce, storage, USDC Transfer को जोड़ता है |
पूर्वापेक्षाएँ
- एक project के साथ Tenderly account।
- Foundry स्थापित (
curl -L https://foundry.paradigm.xyz | bash && foundryup)।
Tenderly Dashboard में, Virtual Environments पर जाएं और एक Virtual Environment बनाएं। Admin RPC URL कॉपी करें, public RPC funding और verification calls पर 401 लौटाता है, इसलिए इस ट्यूटोरियल में हमेशा Admin RPC का उपयोग करें।
// 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);
}
// 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))
);
}
}
// 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();
}
}
VNET_RPC=https://virtual.mainnet.eu.rpc.tenderly.co/<your-virtual-env-uuid>
PRIVATE_KEY=0x...
DEPLOYER=0x...
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"
प्रत्येक call एक अलग transaction है। किसी भी समय अलग arguments के साथ फिर से चलाएं, कोई redeployment आवश्यक नहीं।
# 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"
cast balance 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA --rpc-url "$VNET_RPC"
cast nonce 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 --rpc-url "$VNET_RPC"
cast storage "$TARGET" 0 --rpc-url "$VNET_RPC"
receipt खींचें और
logs[] में एक entry जाँचें जिसका address उस contract से match करता है जिसे आपने spoof किया है:{
"address": "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000cccccccccccccccccccccccccccccccccccccccc",
"0x000000000000000000000000dddddddddddddddddddddddddddddddddddddddd",
"0x00000000000000000000000000000000000000000000000000000000000004d2"
],
"data": "0x"
}
address BAYC contract से match करता है, topics[0] Transfer event signature hash है, और topics[1–3] indexed from, to, और tokenId को वहन करते हैं। BAYC transfers के लिए listen कर रहा एक indexer इस log को एक वास्तविक emission की तरह प्रोसेस करेगा।यह भी देखें
- Solidity Cheatcodes reference, पूर्ण interface परिभाषा और function table।
- Admin RPC, JSON-RPC के माध्यम से transaction के बाहर से state manipulation।