diff --git a/client/debug/debug.go b/client/debug/debug.go index 5fe6a6da..62faedda 100644 --- a/client/debug/debug.go +++ b/client/debug/debug.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - evmdconfig "github.com/cosmos/evm/cmd/evmd/config" "github.com/cosmos/evm/ethereum/eip712" "github.com/cosmos/cosmos-sdk/client" @@ -165,10 +164,10 @@ func RawBytesCmd() *cobra.Command { // LegacyEIP712Cmd outputs types of legacy EIP712 typed data func LegacyEIP712Cmd() *cobra.Command { return &cobra.Command{ - Use: "legacy-eip712 [file]", + Use: "legacy-eip712 [file] [evm-chain-id]", Short: "Output types of legacy eip712 typed data according to the given transaction", - Example: fmt.Sprintf(`$ %s debug legacy-eip712 tx.json --chain-id evmd-1`, version.AppName), - Args: cobra.ExactArgs(1), + Example: fmt.Sprintf(`$ %s debug legacy-eip712 tx.json 4221 --chain-id evmd-1`, version.AppName), + Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { @@ -185,7 +184,12 @@ func LegacyEIP712Cmd() *cobra.Command { return errors.Wrap(err, "encode tx") } - td, err := eip712.LegacyWrapTxToTypedData(clientCtx.Codec, evmdconfig.EVMChainID, stdTx.GetMsgs()[0], txBytes, nil) + evmChainID, err := strconv.Atoi(args[0]) + if err != nil { + return errors.Wrap(err, "parse evm-chain-id") + } + + td, err := eip712.LegacyWrapTxToTypedData(clientCtx.Codec, uint64(evmChainID), stdTx.GetMsgs()[0], txBytes, nil) //nolint:gosec // G115 // overflow not a concern if err != nil { return errors.Wrap(err, "wrap tx to typed data") } diff --git a/cmd/evmd/cmd/root.go b/cmd/evmd/cmd/root.go index 48783d05..e95e891b 100644 --- a/cmd/evmd/cmd/root.go +++ b/cmd/evmd/cmd/root.go @@ -63,7 +63,7 @@ func NewRootCmd() *cobra.Command { nil, true, simtestutil.EmptyAppOptions{}, - cosmosevmserverconfig.DefaultEVMChainID, + evmdconfig.EVMChainID, testutil.NoOpEvmAppOptions, ) @@ -149,7 +149,7 @@ func NewRootCmd() *cobra.Command { } if initClientCtx.ChainID != "" { - if err := evmd.EvmAppOptions(cosmosevmserverconfig.DefaultEVMChainID); err != nil { + if err := evmd.EvmAppOptions(evmdconfig.EVMChainID); err != nil { panic(err) } } diff --git a/cmd/evmd/config/config.go b/cmd/evmd/config/config.go index 4c9caf65..304459a4 100644 --- a/cmd/evmd/config/config.go +++ b/cmd/evmd/config/config.go @@ -23,6 +23,12 @@ var ChainsCoinInfo = map[uint64]evmtypes.EvmCoinInfo{ DisplayDenom: "test", Decimals: evmtypes.EighteenDecimals, }, + EVMChainID: { + Denom: "atest", + ExtendedDenom: "atest", + DisplayDenom: "test", + Decimals: evmtypes.EighteenDecimals, + }, } const ( @@ -47,7 +53,7 @@ const ( // BaseDenomUnit defines the precision of the base denomination. BaseDenomUnit = 18 // EVMChainID defines the EIP-155 replay-protection chain id for the current ethereum chain config. - EVMChainID = 262144 + EVMChainID = 4221 ) // SetBech32Prefixes sets the global prefixes to be used when serializing addresses and public keys to Bech32 strings. diff --git a/rpc/apis.go b/rpc/apis.go index c2651c2d..5ddab393 100644 --- a/rpc/apis.go +++ b/rpc/apis.go @@ -87,12 +87,12 @@ func init() { }, } }, - NetNamespace: func(_ *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool, _ types.EVMTxIndexer) []rpc.API { + NetNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient, _ bool, _ types.EVMTxIndexer) []rpc.API { return []rpc.API{ { Namespace: NetNamespace, Version: apiVersion, - Service: net.NewPublicAPI(clientCtx), + Service: net.NewPublicAPI(ctx, clientCtx), Public: true, }, } diff --git a/rpc/backend/backend_suite_test.go b/rpc/backend/backend_suite_test.go index 91c7f9d3..8d9153f2 100644 --- a/rpc/backend/backend_suite_test.go +++ b/rpc/backend/backend_suite_test.go @@ -14,12 +14,12 @@ import ( cmtrpctypes "github.com/cometbft/cometbft/rpc/core/types" dbm "github.com/cosmos/cosmos-db" + evmdconfig "github.com/cosmos/evm/cmd/evmd/config" "github.com/cosmos/evm/crypto/hd" "github.com/cosmos/evm/encoding" "github.com/cosmos/evm/indexer" "github.com/cosmos/evm/rpc/backend/mocks" rpctypes "github.com/cosmos/evm/rpc/types" - "github.com/cosmos/evm/server/config" "github.com/cosmos/evm/testutil/constants" testnetwork "github.com/cosmos/evm/testutil/integration/os/network" utiltx "github.com/cosmos/evm/testutil/tx" @@ -50,6 +50,7 @@ var ChainID = constants.ExampleChainID func (suite *BackendTestSuite) SetupTest() { ctx := server.NewDefaultContext() ctx.Viper.Set("telemetry.global-labels", []interface{}{}) + ctx.Viper.Set("evm.evm-chain-id", evmdconfig.EVMChainID) baseDir := suite.T().TempDir() nodeDirName := "node" @@ -90,7 +91,7 @@ func (suite *BackendTestSuite) SetupTest() { suite.backend.cfg.JSONRPC.GasCap = 0 suite.backend.cfg.JSONRPC.EVMTimeout = 0 suite.backend.cfg.JSONRPC.AllowInsecureUnlock = true - suite.backend.cfg.EVM.EVMChainID = 262144 + suite.backend.cfg.EVM.EVMChainID = evmdconfig.EVMChainID suite.backend.queryClient.QueryClient = mocks.NewEVMQueryClient(suite.T()) suite.backend.queryClient.FeeMarket = mocks.NewFeeMarketQueryClient(suite.T()) suite.backend.ctx = rpctypes.ContextWithHeight(1) @@ -194,7 +195,7 @@ func (suite *BackendTestSuite) buildFormattedBlock( func (suite *BackendTestSuite) generateTestKeyring(clientDir string) (keyring.Keyring, error) { buf := bufio.NewReader(os.Stdin) - encCfg := encoding.MakeConfig(config.DefaultEVMChainID) + encCfg := encoding.MakeConfig(evmdconfig.EVMChainID) return keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, encCfg.Codec, []keyring.Option{hd.EthSecp256k1Option()}...) } diff --git a/rpc/backend/chain_info_test.go b/rpc/backend/chain_info_test.go index 3bba11be..227b358e 100644 --- a/rpc/backend/chain_info_test.go +++ b/rpc/backend/chain_info_test.go @@ -11,6 +11,7 @@ import ( "github.com/cometbft/cometbft/abci/types" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + evmdconfig "github.com/cosmos/evm/cmd/evmd/config" "github.com/cosmos/evm/rpc/backend/mocks" rpc "github.com/cosmos/evm/rpc/types" utiltx "github.com/cosmos/evm/testutil/tx" @@ -154,7 +155,7 @@ func (suite *BackendTestSuite) TestBaseFee() { } func (suite *BackendTestSuite) TestChainId() { - expChainID := (*hexutil.Big)(big.NewInt(262144)) + expChainID := (*hexutil.Big)(big.NewInt(evmdconfig.EVMChainID)) testCases := []struct { name string registerMock func() diff --git a/rpc/backend/evm_query_client_test.go b/rpc/backend/evm_query_client_test.go index 271cfe7b..ef0cf52b 100644 --- a/rpc/backend/evm_query_client_test.go +++ b/rpc/backend/evm_query_client_test.go @@ -15,9 +15,9 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + evmdconfig "github.com/cosmos/evm/cmd/evmd/config" "github.com/cosmos/evm/rpc/backend/mocks" rpc "github.com/cosmos/evm/rpc/types" - "github.com/cosmos/evm/server/config" utiltx "github.com/cosmos/evm/testutil/tx" evmtypes "github.com/cosmos/evm/x/vm/types" @@ -39,18 +39,18 @@ var _ evmtypes.QueryClient = &mocks.EVMQueryClient{} func RegisterTraceTransactionWithPredecessors(queryClient *mocks.EVMQueryClient, msgEthTx *evmtypes.MsgEthereumTx, predecessors []*evmtypes.MsgEthereumTx) { data := []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x7d} queryClient.On("TraceTx", rpc.ContextWithHeight(1), - &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, Predecessors: predecessors, ChainId: config.DefaultEVMChainID, BlockMaxGas: -1}). + &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, Predecessors: predecessors, ChainId: evmdconfig.EVMChainID, BlockMaxGas: -1}). Return(&evmtypes.QueryTraceTxResponse{Data: data}, nil) } func RegisterTraceTransaction(queryClient *mocks.EVMQueryClient, msgEthTx *evmtypes.MsgEthereumTx) { data := []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x7d} - queryClient.On("TraceTx", rpc.ContextWithHeight(1), &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, ChainId: config.DefaultEVMChainID, BlockMaxGas: -1}). + queryClient.On("TraceTx", rpc.ContextWithHeight(1), &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, ChainId: evmdconfig.EVMChainID, BlockMaxGas: -1}). Return(&evmtypes.QueryTraceTxResponse{Data: data}, nil) } func RegisterTraceTransactionError(queryClient *mocks.EVMQueryClient, msgEthTx *evmtypes.MsgEthereumTx) { - queryClient.On("TraceTx", rpc.ContextWithHeight(1), &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, ChainId: config.DefaultEVMChainID}). + queryClient.On("TraceTx", rpc.ContextWithHeight(1), &evmtypes.QueryTraceTxRequest{Msg: msgEthTx, BlockNumber: 1, ChainId: evmdconfig.EVMChainID}). Return(nil, errortypes.ErrInvalidRequest) } @@ -58,7 +58,7 @@ func RegisterTraceTransactionError(queryClient *mocks.EVMQueryClient, msgEthTx * func RegisterTraceBlock(queryClient *mocks.EVMQueryClient, txs []*evmtypes.MsgEthereumTx) { data := []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x7d} queryClient.On("TraceBlock", rpc.ContextWithHeight(1), - &evmtypes.QueryTraceBlockRequest{Txs: txs, BlockNumber: 1, TraceConfig: &evmtypes.TraceConfig{}, ChainId: config.DefaultEVMChainID, BlockMaxGas: -1}). + &evmtypes.QueryTraceBlockRequest{Txs: txs, BlockNumber: 1, TraceConfig: &evmtypes.TraceConfig{}, ChainId: evmdconfig.EVMChainID, BlockMaxGas: -1}). Return(&evmtypes.QueryTraceBlockResponse{Data: data}, nil) } diff --git a/rpc/namespaces/ethereum/net/api.go b/rpc/namespaces/ethereum/net/api.go index 9e152e31..d1c8f39f 100644 --- a/rpc/namespaces/ethereum/net/api.go +++ b/rpc/namespaces/ethereum/net/api.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/evm/server/config" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" ) // PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. @@ -18,8 +19,8 @@ type PublicAPI struct { } // NewPublicAPI creates an instance of the public Net Web3 API. -func NewPublicAPI(clientCtx client.Context) *PublicAPI { - cfg, err := config.GetConfig(clientCtx.Viper) +func NewPublicAPI(ctx *server.Context, clientCtx client.Context) *PublicAPI { + cfg, err := config.GetConfig(ctx.Viper) if err != nil { panic(err) } diff --git a/server/config/config.go b/server/config/config.go index 605d8047..8bb8df2e 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -63,6 +63,7 @@ const ( // DefaultMaxTxGasWanted is the default gas wanted for each eth tx returned in ante handler in check tx mode DefaultMaxTxGasWanted = 0 + // DefaultEVMChainID is the default EVM Chain ID if one is not provided DefaultEVMChainID = 262144 // DefaultGasCap is the default cap on gas that can be used in eth_call/estimateGas diff --git a/server/config/toml.go b/server/config/toml.go index a007290c..3994d3ce 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -19,6 +19,9 @@ max-tx-gas-wanted = {{ .EVM.MaxTxGasWanted }} # EnablePreimageRecording enables tracking of SHA3 preimages in the VM cache-preimage = {{ .EVM.EnablePreimageRecording }} +# EVMChainID is the EIP-155 compatible replay protection chain ID. This is separate from the Cosmos chain ID. +evm-chain-id = {{ .EVM.EVMChainID }} + ############################################################################### ### JSON RPC Configuration ### ############################################################################### diff --git a/server/flags/flags.go b/server/flags/flags.go index 9093182b..b4eebbfa 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -64,6 +64,7 @@ const ( EVMTracer = "evm.tracer" EVMMaxTxGasWanted = "evm.max-tx-gas-wanted" EVMEnablePreimageRecording = "evm.cache-preimage" + EVMChainID = "evm.evm-chain-id" ) // TLS flags diff --git a/server/start.go b/server/start.go index d885bddb..61dbca62 100644 --- a/server/start.go +++ b/server/start.go @@ -203,6 +203,7 @@ which accepts a path for the resulting pprof file. cmd.Flags().String(srvflags.EVMTracer, cosmosevmserverconfig.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)") //nolint:lll cmd.Flags().Uint64(srvflags.EVMMaxTxGasWanted, cosmosevmserverconfig.DefaultMaxTxGasWanted, "the gas wanted for each eth tx returned in ante handler in check tx mode") //nolint:lll cmd.Flags().Bool(srvflags.EVMEnablePreimageRecording, cosmosevmserverconfig.DefaultEnablePreimageRecording, "Enables tracking of SHA3 preimages in the EVM (not implemented yet)") //nolint:lll + cmd.Flags().Uint64(srvflags.EVMChainID, cosmosevmserverconfig.DefaultEVMChainID, "the EIP-155 compatible replay protection chain ID") cmd.Flags().String(srvflags.TLSCertPath, "", "the cert.pem file path for the server TLS configuration") cmd.Flags().String(srvflags.TLSKeyPath, "", "the key.pem file path for the server TLS configuration") diff --git a/tests/solidity/suites/precompiles/hardhat.config.js b/tests/solidity/suites/precompiles/hardhat.config.js index 6f99b694..80f5f71b 100644 --- a/tests/solidity/suites/precompiles/hardhat.config.js +++ b/tests/solidity/suites/precompiles/hardhat.config.js @@ -16,7 +16,7 @@ module.exports = { networks: { cosmos: { url: "http://127.0.0.1:8545", - chainId: 262144, + chainId: 4221, accounts: [ "0x88CBEAD91AEE890D27BF06E003ADE3D4E952427E88F88D31D61D3EF5E5D54305", "0x3B7955D25189C99A7468192FCBC6429205C158834053EBE3F78F4512AB432DB9", diff --git a/tests/solidity/test-helper.js b/tests/solidity/test-helper.js index ed68f564..12a0d8df 100644 --- a/tests/solidity/test-helper.js +++ b/tests/solidity/test-helper.js @@ -15,28 +15,143 @@ function panic (errMsg) { process.exit(-1) } +// Function to extract EVMChainID from Go config file +function extractChainIDFromGo(goFilePath) { + try { + if (!fs.existsSync(goFilePath)) { + logger.warn(`Go config file not found at ${goFilePath}, using default chain ID: 262144`) + return 262144 + } + + const goFileContent = fs.readFileSync(goFilePath, 'utf8') + + // Look for EVMChainID = number + const chainIdMatch = goFileContent.match(/EVMChainID\s*=\s*(\d+)/) + + if (chainIdMatch) { + const chainId = parseInt(chainIdMatch[1], 10) + logger.info(`Extracted EVMChainID from Go config: ${chainId}`) + return chainId + } + + logger.warn('EVMChainID not found in Go file, using default: 262144') + return 262144 + } catch (error) { + logger.warn(`Error reading Go config file: ${error.message}, using default: 262144`) + return 262144 + } +} + +// Function to update Hardhat config with the extracted chain ID +function updateHardhatConfig(chainId, hardhatConfigPath) { + try { + if (!fs.existsSync(hardhatConfigPath)) { + logger.warn(`Hardhat config not found at ${hardhatConfigPath}`) + return + } + + let configContent = fs.readFileSync(hardhatConfigPath, 'utf8') + + // Find the cosmos network block and update chainId within it + const cosmosBlockRegex = /cosmos:\s*{([^{}]*(?:{[^{}]*}[^{}]*)*)}/ + const match = configContent.match(cosmosBlockRegex) + + if (match) { + const cosmosBlock = match[1] + const chainIdRegex = /chainId:\s*\d+/ + + if (chainIdRegex.test(cosmosBlock)) { + const updatedCosmosBlock = cosmosBlock.replace(chainIdRegex, `chainId: ${chainId}`) + const updatedContent = configContent.replace(cosmosBlockRegex, `cosmos: {${updatedCosmosBlock}}`) + + fs.writeFileSync(hardhatConfigPath, updatedContent) + logger.info(`Updated Hardhat config with chainId: ${chainId}`) + } else { + logger.warn('chainId not found in cosmos network block') + logger.info('Cosmos block content:', cosmosBlock) + } + } else { + logger.warn('Could not find cosmos network block in Hardhat config') + logger.info('Please check if your Hardhat config has a cosmos network configuration') + + // Show available network blocks for debugging + const networkMatches = configContent.match(/\w+:\s*{[^{}]*}/g) + if (networkMatches) { + logger.info('Found network blocks:', networkMatches) + } + } + } catch (error) { + logger.warn(`Error updating Hardhat config: ${error.message}`) + } +} + +// Function to create backup of Hardhat config +function backupHardhatConfig(hardhatConfigPath) { + const backupPath = hardhatConfigPath + '.backup' + try { + if (fs.existsSync(hardhatConfigPath)) { + fs.copyFileSync(hardhatConfigPath, backupPath) + logger.info(`Created backup: ${backupPath}`) + return backupPath + } + } catch (error) { + logger.warn(`Error creating backup: ${error.message}`) + } + return null +} + +// Function to restore Hardhat config from backup +function restoreHardhatConfig(hardhatConfigPath, backupPath) { + try { + if (backupPath && fs.existsSync(backupPath)) { + fs.copyFileSync(backupPath, hardhatConfigPath) + fs.unlinkSync(backupPath) // Remove backup file + logger.info('Restored original Hardhat config') + } + } catch (error) { + logger.warn(`Error restoring config: ${error.message}`) + } +} + +// Function to sync configuration from Go to Hardhat +function syncConfiguration() { + // Adjust these paths based on your project structure + const goConfigPath = path.join(__dirname, '../../cmd/evmd/config/config.go') + const hardhatConfigPath = path.join(__dirname, './suites/precompiles/hardhat.config.js') + + logger.info('Syncing configuration from Go to Hardhat...') + + // Create backup before modifying + const backupPath = backupHardhatConfig(hardhatConfigPath) + + const chainId = extractChainIDFromGo(goConfigPath) + updateHardhatConfig(chainId, hardhatConfigPath) + + return { hardhatConfigPath, backupPath } +} + function checkTestEnv () { const argv = yargs(hideBin(process.argv)) - .usage('Usage: $0 [options] ') - .example('$0 --network cosmos', 'run all tests using cosmos evm network') - .example( - '$0 --network cosmos --allowTests=test1,test2', - 'run only test1 and test2 using cosmos network' - ) - .help('h') - .alias('h', 'help') - .describe('network', 'set which network to use: ganache|cosmos') - .describe( - 'batch', - 'set the test batch in parallelized testing. Format: %d-%d' - ) - .describe('allowTests', 'only run specified tests. Separated by comma.') - .boolean('verbose-log') - .describe('verbose-log', 'print evmd output, default false').argv + .usage('Usage: $0 [options] ') + .example('$0 --network cosmos', 'run all tests using cosmos evm network') + .example( + '$0 --network cosmos --allowTests=test1,test2', + 'run only test1 and test2 using cosmos network' + ) + .help('h') + .alias('h', 'help') + .describe('network', 'set which network to use: ganache|cosmos') + .describe( + 'batch', + 'set the test batch in parallelized testing. Format: %d-%d' + ) + .describe('allowTests', 'only run specified tests. Separated by comma.') + .boolean('verbose-log') + .describe('verbose-log', 'print evmd output, default false').argv if (!fs.existsSync(path.join(__dirname, './node_modules'))) { panic( - 'node_modules not existed. Please run `yarn install` before running tests.' + 'node_modules not existed. Please run `yarn install` before running tests.' ) } const runConfig = {} @@ -54,8 +169,8 @@ function checkTestEnv () { if (argv.batch) { const [toRunBatch, allBatches] = argv.batch - .split('-') - .map((e) => Number(e)) + .split('-') + .map((e) => Number(e)) console.log([toRunBatch, allBatches]) if (!toRunBatch || !allBatches) { @@ -77,8 +192,8 @@ function checkTestEnv () { // only test runConfig.onlyTest = argv.allowTests - ? argv.allowTests.split(',') - : undefined + ? argv.allowTests.split(',') + : undefined runConfig.verboseLog = !!argv['verbose-log'] logger.info(`Running on network: ${runConfig.network}`) @@ -98,7 +213,7 @@ function loadTests (runConfig) { for (const f of needFiles) { if (!fs.existsSync(path.join(__dirname, 'suites', dirname, f))) { logger.warn( - `${dirname} does not contains file/dir: ${f}. Skip this test suite.` + `${dirname} does not contains file/dir: ${f}. Skip this test suite.` ) return } @@ -107,23 +222,23 @@ function loadTests (runConfig) { // test package.json try { const testManifest = JSON.parse( - fs.readFileSync( - path.join(__dirname, 'suites', dirname, 'package.json'), - 'utf-8' - ) + fs.readFileSync( + path.join(__dirname, 'suites', dirname, 'package.json'), + 'utf-8' + ) ) const needScripts = ['test-ganache', 'test-cosmos'] for (const s of needScripts) { if (Object.keys(testManifest.scripts).indexOf(s) === -1) { logger.warn( - `${dirname} does not have test script: \`${s}\`. Skip this test suite.` + `${dirname} does not have test script: \`${s}\`. Skip this test suite.` ) return } } } catch (error) { logger.warn( - `${dirname} test package.json load failed. Skip this test suite.` + `${dirname} test package.json load failed. Skip this test suite.` ) logger.err(error) return @@ -138,10 +253,10 @@ function loadTests (runConfig) { if (runConfig.batch) { const chunkSize = Math.ceil(validTests.length / runConfig.batch.all) const toRunTests = validTests.slice( - (runConfig.batch.this - 1) * chunkSize, - runConfig.batch.this === runConfig.batch.all - ? undefined - : runConfig.batch.this * chunkSize + (runConfig.batch.this - 1) * chunkSize, + runConfig.batch.this === runConfig.batch.all + ? undefined + : runConfig.batch.this * chunkSize ) return toRunTests } else { @@ -240,31 +355,81 @@ function setupNetwork ({ runConfig, timeout }) { } async function main () { - const runConfig = checkTestEnv() - const allTests = loadTests(runConfig) + // Sync configuration before running tests + const configPaths = syncConfiguration() - console.log(`Running Tests: ${allTests.join()}`) + let proc = null - const proc = await setupNetwork({ runConfig, timeout: 50000 }) + try { + const runConfig = checkTestEnv() + const allTests = loadTests(runConfig) - // sleep for 20s to wait blocks being produced - // - // TODO: this should be handled more gracefully, i.e. check for block height - await new Promise((resolve) => setTimeout(resolve, 20000)) + console.log(`Running Tests: ${allTests.join()}`) - await performTests({ allTests, runConfig }) + proc = await setupNetwork({ runConfig, timeout: 50000 }) + + // sleep for 20s to wait blocks being produced + // + // TODO: this should be handled more gracefully, i.e. check for block height + await new Promise((resolve) => setTimeout(resolve, 20000)) + + await performTests({ allTests, runConfig }) + + logger.info('Tests completed successfully!') + } catch (error) { + logger.err(`Test execution failed: ${error.message}`) + throw error + } finally { + // Always restore the original config, even if tests fail + if (configPaths) { + restoreHardhatConfig(configPaths.hardhatConfigPath, configPaths.backupPath) + } - if (proc) { - proc.kill() + if (proc) { + proc.kill() + } } + process.exit(0) } // Add handler to exit the program when UnhandledPromiseRejection - process.on('unhandledRejection', (e) => { console.error(e) + + // Try to restore config if possible + const hardhatConfigPath = path.join(__dirname, 'hardhat.config.js') + const backupPath = hardhatConfigPath + '.backup' + if (fs.existsSync(backupPath)) { + try { + fs.copyFileSync(backupPath, hardhatConfigPath) + fs.unlinkSync(backupPath) + logger.info('Restored original Hardhat config after error') + } catch (restoreError) { + logger.warn(`Could not restore config: ${restoreError.message}`) + } + } + process.exit(-1) }) -main() +// Handle SIGINT (Ctrl+C) to restore config +process.on('SIGINT', () => { + logger.info('Received SIGINT, cleaning up...') + + const hardhatConfigPath = path.join(__dirname, 'hardhat.config.js') + const backupPath = hardhatConfigPath + '.backup' + if (fs.existsSync(backupPath)) { + try { + fs.copyFileSync(backupPath, hardhatConfigPath) + fs.unlinkSync(backupPath) + logger.info('Restored original Hardhat config') + } catch (restoreError) { + logger.warn(`Could not restore config: ${restoreError.message}`) + } + } + + process.exit(0) +}) + +main() \ No newline at end of file