From c7e3d4be60ba6296b29d1a127b4f02617a8fa4cb Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Sun, 20 Aug 2023 15:34:16 +0100 Subject: [PATCH] Allow to define storage to debug tenderly actions --- .env.example | 5 ++ actions/test/run_local.ts | 134 ++++++++++++++++++++++++++------------ 2 files changed, 99 insertions(+), 40 deletions(-) diff --git a/.env.example b/.env.example index 6d7ca3a..4e8664d 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,11 @@ NETWORK=5 # Network to test. 1: Mainnet, 5: Goerli, 100: xDai #BLOCK_NUMBER=9500767 BLOCK_NUMBER= +# Optionally, specify the storage process locally +# - useful for debugging a failed execution in Tenderly +# - you can pass the value of the "storage" field from production to try to reproduce the error locally +STORAGE= + # Slack SLACK_WEBHOOK_URL= NOTIFICATIONS_ENABLED=false diff --git a/actions/test/run_local.ts b/actions/test/run_local.ts index 0d15324..1317822 100644 --- a/actions/test/run_local.ts +++ b/actions/test/run_local.ts @@ -8,55 +8,43 @@ import { addContract } from "../addContract"; import { ethers } from "ethers"; import assert = require("assert"); import { getProvider } from "../utils"; +import { getOrdersStorageKey } from "../model"; +import { exit } from "process"; require("dotenv").config(); const main = async () => { - const testRuntime = new TestRuntime(); - // The web3 actions fetches the node url and computes the API based on the current chain id const network = process.env.NETWORK; assert(network, "network is required"); - // Add secrets from local env (.env) for current network - const envNames = [ - `NODE_URL_${network}`, - `NODE_USER_${network}`, - `NODE_PASSWORD_${network}`, - "SLACK_WEBHOOK_URL", - "NOTIFICATIONS_ENABLED", - "SENTRY_DSN", - ]; - for (const name of envNames) { - const envValue = process.env[name]; - if (envValue) { - await testRuntime.context.secrets.put(name, envValue); - } - } + const testRuntime = await _getRunTime(network); // Get provider const provider = await getProvider(testRuntime.context, network); const { chainId } = await provider.getNetwork(); - const onNewBlock = async (blockNumber: number) => { - try { - processBlock(provider, blockNumber, chainId, testRuntime); - } catch (error) { - console.error("[run_local] Error in processBlock", error); - } - }; - // Run one of the 2 Execution modes (single block, or watch mode) if (process.env.BLOCK_NUMBER) { // Execute once, for a specific block const blockNumber = Number(process.env.BLOCK_NUMBER); console.log(`[run_local] Processing specific block ${blockNumber}...`); - await onNewBlock(blockNumber).catch(console.error); + await processBlock(provider, blockNumber, chainId, testRuntime).catch( + () => { + exit(100); + } + ); console.log(`[run_local] Block ${blockNumber} has been processed.`); } else { // Watch for new blocks console.log(`[run_local] Subscribe to new blocks for network ${network}`); - provider.on("block", onNewBlock); + provider.on("block", async (blockNumber: number) => { + try { + await processBlock(provider, blockNumber, chainId, testRuntime); + } catch (error) { + console.error("[run_local] Error in processBlock", error); + } + }); } }; @@ -72,41 +60,57 @@ async function processBlock( const blockWithTransactions = await provider.getBlockWithTransactions( blockNumber ); + let hasErrors = false; for (const transaction of blockWithTransactions.transactions) { const receipt = await provider.getTransactionReceipt(transaction.hash); if (receipt) { + const { + hash, + from, + value, + nonce, + gasLimit, + maxPriorityFeePerGas, + maxFeePerGas, + } = transaction; + const testTransactionEvent: TestTransactionEvent = { blockHash: block.hash, blockNumber: block.number, - from: transaction.from, - hash: transaction.hash, + from, + hash, network: chainId.toString(), logs: receipt.logs, input: "", - value: transaction.value.toString(), - nonce: transaction.nonce.toString(), - gas: transaction.gasLimit.toString(), + value: value.toString(), + nonce: nonce.toString(), + gas: gasLimit.toString(), gasUsed: receipt.gasUsed.toString(), cumulativeGasUsed: receipt.cumulativeGasUsed.toString(), gasPrice: receipt.effectiveGasPrice.toString(), - gasTipCap: transaction.maxPriorityFeePerGas - ? transaction.maxPriorityFeePerGas.toString() - : "", - gasFeeCap: transaction.maxFeePerGas - ? transaction.maxFeePerGas.toString() - : "", + gasTipCap: maxPriorityFeePerGas ? maxPriorityFeePerGas.toString() : "", + gasFeeCap: maxFeePerGas ? maxFeePerGas.toString() : "", transactionHash: transaction.hash, }; // run action - await testRuntime + console.log(`[run_local] AddContract for TX ${hash}`); + const result = await testRuntime .execute(addContract, testTransactionEvent) + .then(() => true) .catch((e) => { + hasErrors = true; console.error( "[run_local] Error in addContract processing transaction:", e ); + return false; }); + console.log( + `[run_local] AddContract for TX ${hash}. Result ${_formatResult( + result + )}` + ); } } @@ -118,14 +122,64 @@ async function processBlock( testBlockEvent.network = chainId.toString(); // run action - await testRuntime + console.log(`[run_local] checkForAndPlaceOrder for block ${blockNumber}`); + const result = await testRuntime .execute(checkForAndPlaceOrder, testBlockEvent) + .then(() => true) .catch((e) => { + hasErrors = true; console.log( "[run_local] Error in checkForAndPlaceOrder processing block", e ); + return false; }); + console.log( + `[run_local] checkForAndPlaceOrder for block ${blockNumber}. Result ${_formatResult( + result + )}` + ); + + if (hasErrors) { + throw new Error("[run_local] Errors found in processing block"); + } +} + +async function _getRunTime(network: string): Promise { + const testRuntime = new TestRuntime(); + + // Add secrets from local env (.env) for current network + const envNames = [ + `NODE_URL_${network}`, + `NODE_USER_${network}`, + `NODE_PASSWORD_${network}`, + "SLACK_WEBHOOK_URL", + "NOTIFICATIONS_ENABLED", + "SENTRY_DSN", + ]; + for (const name of envNames) { + const envValue = process.env[name]; + if (envValue) { + await testRuntime.context.secrets.put(name, envValue); + } + } + + // Load storage from env + const storage = process.env.STORAGE; + if (storage) { + const storageFormatted = JSON.stringify(JSON.parse(storage), null, 2); + console.log("[run_local] Loading storage from env", storageFormatted); + await testRuntime.context.storage.putStr( + getOrdersStorageKey(network), + storage + ); + } + + return testRuntime; +} + +function _formatResult(result: boolean) { + return result ? "✅" : "❌"; } (async () => await main())();