Разработка Tenderly Fork для тестирования
Tenderly Fork — это управляемый fork основной сети с REST API для манипуляции состоянием: изменить баланс любого адреса, имперсонировать любой аккаунт, перемотать время, выставить хранилище контракта напрямую. В отличие от anvil --fork-url, Tenderly Fork живёт в облаке, доступен по URL и может быть расшарен команде или передан аудитору.
Когда нужен Tenderly Fork, а не anvil
anvil --fork-url запускается локально и умирает вместе с процессом. Tenderly Fork создаётся через API, живёт пока не удалён, его RPC URL можно передать фронтенду или мобильному приложению.
Реальный кейс: контракт vault проходит аудит. Аудиторская команда хочет воспроизвести конкретный attack scenario — drain через flash loan в конкретном состоянии протокола. Вместо того чтобы настраивать локальную среду у каждого аудитора, создаётся Tenderly Fork, закрепляется на блоке с нужным состоянием, выставляется нужный баланс атакующего. Аудитор получает один RPC URL.
Создание Fork через API
const response = await fetch("https://api.tenderly.co/api/v1/account/MY_ACCOUNT/project/MY_PROJECT/fork", {
method: "POST",
headers: {
"X-Access-Key": process.env.TENDERLY_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
network_id: "1", // Ethereum mainnet
block_number: 19500000, // конкретный блок
transaction_index: 0,
initial_balance: 100,
chain_config: {
chain_id: 1
}
})
});
const { simulation_fork } = await response.json();
const forkRpcUrl = `https://rpc.tenderly.co/fork/${simulation_fork.id}`;
Манипуляция состоянием через JSON-RPC
Tenderly Fork поддерживает нестандартные методы:
// Установить баланс адреса
await provider.send("tenderly_setBalance", [
["0xUserAddress"],
"0x56BC75E2D63100000" // 100 ETH в hex
]);
// Имперсонировать аккаунт (подписывать от его имени)
await provider.send("tenderly_addBalance", [
["0xWhaleAddress"],
"0xDE0B6B3A7640000"
]);
// Установить значение в storage напрямую
await provider.send("tenderly_setStorageAt", [
contractAddress,
storageSlot, // keccak256 slot
newValue
]);
// Изменить timestamp
await provider.send("evm_setNextBlockTimestamp", [futureTimestamp]);
await provider.send("evm_mine", []);
Прямая запись в storage — мощный инструмент для тестирования: выставить paused = true в контракте без вызова pause(), симулировать что пользователь уже вложил $1M, обойти cooldown period.
Интеграция с Foundry
# Запуск тестов против Tenderly Fork
forge test --fork-url $TENDERLY_FORK_RPC --fork-block-number 19500000 -vvv
В Foundry тестах можно использовать vm.prank(whale) и vm.deal(attacker, 1000 ether) — они работают через Tenderly Fork так же как через anvil. Разница: состояние сохраняется между запусками если форк не пересоздан.
Симуляция транзакций без отправки
Tenderly Simulate API позволяет симулировать транзакцию и получить детальный trace до отправки на mainnet:
const simulation = await fetch(`https://api.tenderly.co/api/v1/account/${account}/project/${project}/simulate`, {
method: "POST",
headers: { "X-Access-Key": apiKey },
body: JSON.stringify({
network_id: "1",
from: senderAddress,
to: contractAddress,
input: calldata,
gas: 500000,
value: "0",
save: true // сохранить для просмотра в dashboard
})
});
Результат — полный call trace с газом на каждый вызов, storage changes, events. Это дешевле реального деплоя на mainnet для отладки сложных сценариев.
Типичные сценарии
Тестирование апгрейда на mainnet-состоянии. Fork на блок перед апгрейдом, выполняем upgrade транзакцию, проверяем что все пользовательские позиции читаются корректно новой имплементацией.
Воспроизведение инцидента. Форк на блок до взлома, воспроизводим вектор атаки. Находим root cause. Патчим. Проверяем что пропатченная версия устойчива.
Демо для инвесторов. Создаём Fork с нужным начальным состоянием (пользователи, позиции, балансы), передаём RPC URL. Инвестор взаимодействует с протоколом как с mainnet, но без реальных средств.
Сроки
Настройка Tenderly Fork с базовыми скриптами манипуляции состоянием: 1 день. Полная тестовая инфраструктура с автоматическим созданием форков в CI и воспроизведением сценариев: 2-3 дня.
Стоимость рассчитывается после уточнения сложности тестовых сценариев и требований к интеграции с CI.







