From 8f5b5fecb959da30ccf6a2256b66e028207496c7 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Thu, 2 Oct 2025 16:32:11 +0200 Subject: [PATCH 01/14] chore: boilerplate --- .../script/test/7683/BurstLoadTest.s.sol | 242 ++++++++++++ contracts/script/test/7683/README.md | 166 ++++++++ .../script/test/7683/SustainedLoadTest.s.sol | 363 ++++++++++++++++++ contracts/script/test/7683/run-load-test.sh | 243 ++++++++++++ 4 files changed, 1014 insertions(+) create mode 100644 contracts/script/test/7683/BurstLoadTest.s.sol create mode 100644 contracts/script/test/7683/README.md create mode 100644 contracts/script/test/7683/SustainedLoadTest.s.sol create mode 100755 contracts/script/test/7683/run-load-test.sh diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol new file mode 100644 index 00000000..4cea3ab7 --- /dev/null +++ b/contracts/script/test/7683/BurstLoadTest.s.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Script } from "forge-std/Script.sol"; +import { console2 } from "forge-std/console2.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; +import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; +import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; +import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; +import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; + +/** + * @title BurstLoadTest + * @dev Load testing script for 7683 contracts that sends a configurable number of transactions + * sequentially to avoid RPC rate limiting. Supports both Arbitrum->Base and Base->Arbitrum flows. + */ +contract BurstLoadTest is Script { + // Configuration from environment variables + uint256 public constant MAX_TRANSACTIONS = 10; // Default, can be overridden by env var + uint256 public constant AMOUNT_IN = 10; // 0.00001 USDT (assuming 6 decimals) + + // Test results tracking + struct TestResult { + bytes32 orderId; + uint256 txHash; + uint256 timestamp; + bool success; + } + + TestResult[] public results; + uint256 public successCount; + uint256 public failureCount; + + event TransactionSent(bytes32 indexed orderId, uint256 txHash, bool success); + event LoadTestComplete(uint256 totalTransactions, uint256 successCount, uint256 failureCount); + + function run() external { + // Get configuration from environment + uint256 maxTxs = vm.envOr("LOAD_TEST_MAX_TRANSACTIONS", uint256(MAX_TRANSACTIONS)); + string memory testDirection = vm.envOr("LOAD_TEST_DIRECTION", string("arbitrum_to_base")); + + console2.log("Starting load test with", maxTxs, "transactions"); + console2.log("Test direction:", testDirection); + + // Reset counters + delete results; + successCount = 0; + failureCount = 0; + + // Execute load test based on direction + if (keccak256(bytes(testDirection)) == keccak256(bytes("arbitrum_to_base"))) { + _runArbitrumToBaseLoadTest(maxTxs); + } else if (keccak256(bytes(testDirection)) == keccak256(bytes("base_to_arbitrum"))) { + _runBaseToArbitrumLoadTest(maxTxs); + } else { + revert("Invalid test direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'"); + } + + // Print final results + console2.log("=== LOAD TEST COMPLETE ==="); + console2.log("Total transactions:", maxTxs); + console2.log("Successful:", successCount); + console2.log("Failed:", failureCount); + console2.log("Success rate:", (successCount * 100) / maxTxs, "%"); + + emit LoadTestComplete(maxTxs, successCount, failureCount); + } + + function _runArbitrumToBaseLoadTest(uint256 maxTxs) internal { + vm.createSelectFork(vm.rpcUrl("arbitrum_sepolia")); + + T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); + uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); + address alice = vm.addr(alicePk); + + // Approve tokens once at the beginning + vm.startBroadcast(alicePk); + ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); + inputToken.approve(address(l1_7683), type(uint256).max); + vm.stopBroadcast(); + + console2.log("Starting Arbitrum -> Base load test..."); + + // Send transactions sequentially + for (uint256 i = 0; i < maxTxs; i++) { + _sendArbitrumToBaseTransaction(l1_7683, alicePk, alice, i); + + // Small delay to avoid overwhelming the RPC + vm.sleep(100); // 100ms delay between transactions + } + } + + function _runBaseToArbitrumLoadTest(uint256 maxTxs) internal { + vm.createSelectFork(vm.rpcUrl("base_sepolia")); + + T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); + uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); + address alice = vm.addr(alicePk); + + // Approve tokens once at the beginning + vm.startBroadcast(alicePk); + ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); + inputToken.approve(address(l1_7683), type(uint256).max); + vm.stopBroadcast(); + + console2.log("Starting Base -> Arbitrum load test..."); + + // Send transactions sequentially + for (uint256 i = 0; i < maxTxs; i++) { + _sendBaseToArbitrumTransaction(l1_7683, alicePk, alice, i); + + // Small delay to avoid overwhelming the RPC + vm.sleep(100); // 100ms delay between transactions + } + } + + function _sendArbitrumToBaseTransaction( + T1ERC7683 l1_7683, + uint256 alicePk, + address alice, + uint256 nonce + ) internal { + vm.startBroadcast(alicePk); + + ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); + ERC20 outputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); + + // Prepare order data with unique nonce + OrderData memory orderData = OrderData({ + sender: TypeCasts.addressToBytes32(alice), + recipient: TypeCasts.addressToBytes32(alice), + inputToken: TypeCasts.addressToBytes32(address(inputToken)), + outputToken: TypeCasts.addressToBytes32(address(outputToken)), + amountIn: AMOUNT_IN, + minAmountOut: AMOUNT_IN * 9 / 10, + senderNonce: uint32(nonce), + originDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), + destinationDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), + destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")), + fillDeadline: uint32(1800), // 30 minutes from now + closedAuction: true, + data: new bytes(0) + }); + + bytes memory encodedOrder = OrderEncoder.encode(orderData); + + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + + l1_7683.open(order); + + bytes32 id = OrderEncoder.id(orderData); + + results.push(TestResult({ + orderId: id, + txHash: uint256(keccak256(abi.encodePacked(nonce, "burst_test"))), + timestamp: 0, // Not used in load test + success: true + })); + + emit TransactionSent(id, results[results.length - 1].txHash, true); + successCount++; + console2.log("Transaction", nonce + 1, "successful"); + + vm.stopBroadcast(); + } + + function _sendBaseToArbitrumTransaction( + T1ERC7683 l1_7683, + uint256 alicePk, + address alice, + uint256 nonce + ) internal { + vm.startBroadcast(alicePk); + + ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); + ERC20 outputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); + + // Prepare order data with unique nonce + OrderData memory orderData = OrderData({ + sender: TypeCasts.addressToBytes32(alice), + recipient: TypeCasts.addressToBytes32(alice), + inputToken: TypeCasts.addressToBytes32(address(inputToken)), + outputToken: TypeCasts.addressToBytes32(address(outputToken)), + amountIn: AMOUNT_IN, + minAmountOut: AMOUNT_IN * 9 / 10, + senderNonce: uint32(nonce), + originDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), + destinationDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), + destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")), + fillDeadline: uint32(1800), // 30 minutes from now + closedAuction: true, + data: new bytes(0) + }); + + bytes memory encodedOrder = OrderEncoder.encode(orderData); + + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + + l1_7683.open(order); + + bytes32 id = OrderEncoder.id(orderData); + + results.push(TestResult({ + orderId: id, + txHash: uint256(keccak256(abi.encodePacked(nonce, "burst_test"))), + timestamp: 0, // Not used in load test + success: true + })); + + emit TransactionSent(id, results[results.length - 1].txHash, true); + successCount++; + console2.log("Transaction", nonce + 1, "successful"); + + vm.stopBroadcast(); + } + + function _prepareOnchainOrder( + bytes memory orderData, + uint32 fillDeadline, + bytes32 orderDataType + ) + internal + pure + returns (OnchainCrossChainOrder memory) + { + return + OnchainCrossChainOrder({ fillDeadline: fillDeadline, orderDataType: orderDataType, orderData: orderData }); + } + + // Utility functions for analysis + function getResults() external view returns (TestResult[] memory) { + return results; + } + + function getSuccessRate() external view returns (uint256) { + if (successCount + failureCount == 0) return 0; + return (successCount * 100) / (successCount + failureCount); + } +} \ No newline at end of file diff --git a/contracts/script/test/7683/README.md b/contracts/script/test/7683/README.md new file mode 100644 index 00000000..baf48e93 --- /dev/null +++ b/contracts/script/test/7683/README.md @@ -0,0 +1,166 @@ +# 7683 Load Testing Scripts + +This directory contains load testing scripts for the 7683 cross-chain contracts. + +## Scripts + +### BurstLoadTest.s.sol +Sends a configurable number of transactions sequentially as fast as possible for burst load testing. + +### SustainedLoadTest.s.sol +Sends n transactions every t minutes for a sustained load test over a configurable duration. + +### run-load-test.sh +Shell script runner that can execute either burst or sustained load tests with proper configuration. + +## Usage + +### Prerequisites +- Ensure your `.env` file is properly configured with all required variables +- Make sure you have sufficient tokens for testing (USDT on both chains) +- Set the required API keys: `ARBISCAN_API_KEY` and `BASESCAN_API_KEY` + +### Burst Load Testing + +Send a specified number of transactions as fast as possible: + +```bash +# Send 50 transactions from Arbitrum to Base +./run-load-test.sh burst 50 arbitrum_to_base + +# Send 100 transactions from Base to Arbitrum +./run-load-test.sh burst 100 base_to_arbitrum + +# Send 25 transactions (defaults to arbitrum_to_base) +./run-load-test.sh burst 25 +``` + +### Sustained Load Testing + +Send n transactions every t interval for a specified duration with flexible time units: + +```bash +# Send 5 transactions every 2 minutes for 20 minutes (Arbitrum to Base) +./run-load-test.sh sustained 5 2m 20m arbitrum_to_base + +# Send 10 transactions every 30 seconds for 5 minutes (Base to Arbitrum) +./run-load-test.sh sustained 10 30s 5m base_to_arbitrum + +# Send 3 transactions every 1 hour for 2 hours +./run-load-test.sh sustained 3 1h 2h arbitrum_to_base + +# Use defaults: 5 transactions every 1 minute for 10 minutes +./run-load-test.sh sustained +``` + +**Time Units:** +- `s` = seconds (e.g., `30s`) +- `m` = minutes (e.g., `2m`) +- `h` = hours (e.g., `1h`) + +## Environment Variables + +### Required for Both Modes +- `ALICE_PRIVATE_KEY` - Private key for the test account +- `ARBISCAN_API_KEY` - API key for Arbitrum verification +- `BASESCAN_API_KEY` - API key for Base verification + +### Required Contract Addresses +- `ARB_T1_PULL_BASED_7683_PROXY_ADDR` - Arbitrum 7683 contract +- `BASE_T1_PULL_BASED_7683_PROXY_ADDR` - Base 7683 contract +- `ARBITRUM_SEPOLIA_USDT_ADDR` - USDT on Arbitrum Sepolia +- `BASE_SEPOLIA_USDT_ADDR` - USDT on Base Sepolia + +### Optional Configuration +- `LOAD_TEST_MAX_TRANSACTIONS` - Override default max transactions for burst mode +- `SUSTAINED_TXS_PER_BATCH` - Override default transactions per batch for sustained mode +- `SUSTAINED_INTERVAL_MINUTES` - Override default interval for sustained mode +- `SUSTAINED_DURATION_MINUTES` - Override default duration for sustained mode +- `LOAD_TEST_DIRECTION` - Override test direction (arbitrum_to_base or base_to_arbitrum) + +## Test Flow + +Both scripts follow the same basic flow: + +1. **Setup**: Connect to the appropriate RPC and load contract addresses +2. **Token Approval**: Approve the maximum amount of USDT for the 7683 contract +3. **Transaction Execution**: + - Create cross-chain order data with unique nonces + - Call `T1ERC7683.open()` with the order + - Track success/failure rates +4. **Reporting**: Log results and emit events for analysis + +## Monitoring + +### Events Emitted +- `TransactionSent(bytes32 orderId, uint256 batchNumber, bool success)` +- `LoadTestComplete(uint256 totalTransactions, uint256 successCount, uint256 failureCount)` (burst mode) +- `BatchCompleted(uint256 batchNumber, uint256 successCount, uint256 failureCount)` (sustained mode) +- `SessionStarted/Ended` events (sustained mode) + +### Console Output +- Real-time progress updates +- Success/failure counts +- Final statistics including success rates +- Error messages for failed transactions + +## Rate Limiting Considerations + +- **Burst Mode**: Includes 100ms delays between transactions to avoid overwhelming RPC endpoints +- **Sustained Mode**: Configurable intervals between batches to maintain sustained load +- Both modes send transactions sequentially to avoid RPC rate limits + +## Emergency Controls + +### Sustained Mode Emergency Stop +The sustained load test includes an `emergencyStop()` function that can be called to immediately halt an active test session. + +## Example Test Scenarios + +### Quick Burst Test +```bash +# Test with 20 transactions to verify basic functionality +./run-load-test.sh burst 20 arbitrum_to_base +``` + +### Stress Test +```bash +# Send 100 transactions to test system under load +./run-load-test.sh burst 100 arbitrum_to_base +``` + +### Long-term Stability Test +```bash +# Run for 2 hours with 3 transactions every 5 minutes +./run-load-test.sh sustained 3 5m 2h arbitrum_to_base +``` + +### High Frequency Test +```bash +# Send 10 transactions every 30 seconds for 10 minutes +./run-load-test.sh sustained 10 30s 10m arbitrum_to_base +``` + +### Very High Frequency Test +```bash +# Send 5 transactions every 10 seconds for 1 minute +./run-load-test.sh sustained 5 10s 1m arbitrum_to_base +``` + +## Troubleshooting + +### Common Issues +1. **Insufficient tokens**: Ensure Alice's account has enough USDT on both chains +2. **RPC rate limits**: Increase delays in the script if you encounter rate limiting +3. **Contract not deployed**: Verify all contract addresses in your `.env` file +4. **API key issues**: Ensure your Etherscan/Blockscout API keys are valid + +### Debug Mode +Use `-vvv` flag in the shell script for maximum verbosity to debug issues. + +## Safety Notes + +- These scripts send real transactions on testnets +- Monitor your test account balances +- Use appropriate delays to avoid overwhelming RPC endpoints +- Start with small transaction counts to verify functionality diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol new file mode 100644 index 00000000..724d68fd --- /dev/null +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Script } from "forge-std/Script.sol"; +import { console2 } from "forge-std/console2.sol"; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; +import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; +import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; +import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; +import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; + +/** + * @title SustainedLoadTest + * @dev Sustained load testing script for 7683 contracts that sends n transactions + * every t seconds for a configurable duration. Supports both Arbitrum->Base and Base->Arbitrum flows. + */ +contract SustainedLoadTest is Script { + // Configuration from environment variables + uint256 public constant DEFAULT_TRANSACTIONS_PER_BATCH = 5; + uint256 public constant DEFAULT_INTERVAL_SECONDS = 60; // 1 minute + uint256 public constant DEFAULT_DURATION_SECONDS = 600; // 10 minutes + uint256 public constant AMOUNT_IN = 10; // 0.00001 USDT (assuming 6 decimals) + + // Test state + struct TestSession { + uint256 startTime; + uint256 endTime; + uint256 totalBatches; + uint256 transactionsPerBatch; + uint256 intervalMinutes; + bool isActive; + } + + struct BatchResult { + uint256 batchNumber; + uint256 timestamp; + uint256 successCount; + uint256 failureCount; + bytes32[] orderIds; + } + + TestSession public currentSession; + BatchResult[] public batchResults; + uint256 public totalTransactions; + uint256 public totalSuccesses; + uint256 public totalFailures; + uint256 public nonceCounter; + + event SessionStarted(uint256 startTime, uint256 endTime, uint256 transactionsPerBatch, uint256 intervalSeconds); + event BatchCompleted(uint256 batchNumber, uint256 successCount, uint256 failureCount); + event SessionEnded(uint256 totalTransactions, uint256 totalSuccesses, uint256 totalFailures); + event TransactionSent(bytes32 indexed orderId, uint256 batchNumber, bool success); + + function run() external { + // Get configuration from environment + uint256 txsPerBatch = vm.envOr("SUSTAINED_TXS_PER_BATCH", uint256(DEFAULT_TRANSACTIONS_PER_BATCH)); + uint256 intervalSeconds = vm.envOr("SUSTAINED_INTERVAL_SECONDS", uint256(DEFAULT_INTERVAL_SECONDS)); + uint256 durationSeconds = vm.envOr("SUSTAINED_DURATION_SECONDS", uint256(DEFAULT_DURATION_SECONDS)); + string memory testDirection = vm.envOr("LOAD_TEST_DIRECTION", string("arbitrum_to_base")); + + console2.log("=== STARTING SUSTAINED LOAD TEST ==="); + console2.log("Transactions per batch: %d", txsPerBatch); + console2.log("Interval (seconds): %d", intervalSeconds); + console2.log("Duration (seconds): %d", durationSeconds); + console2.log("Test direction: %s", testDirection); + + // Validate configuration + require(txsPerBatch > 0, "Transactions per batch must be > 0"); + require(intervalSeconds > 0, "Interval must be > 0"); + require(durationSeconds > 0, "Duration must be > 0"); + + // Initialize session + currentSession = TestSession({ + startTime: 0, // Not used for timing in script + endTime: durationSeconds, // Duration in seconds + totalBatches: durationSeconds / intervalSeconds, + transactionsPerBatch: txsPerBatch, + intervalMinutes: intervalSeconds / 60, // Convert to minutes for compatibility + isActive: true + }); + + // Reset counters + delete batchResults; + totalTransactions = 0; + totalSuccesses = 0; + totalFailures = 0; + nonceCounter = 0; + + emit SessionStarted(currentSession.startTime, currentSession.endTime, txsPerBatch, intervalSeconds); + + // Execute sustained load test based on direction + if (keccak256(bytes(testDirection)) == keccak256(bytes("arbitrum_to_base"))) { + _runSustainedArbitrumToBaseTest(); + } else if (keccak256(bytes(testDirection)) == keccak256(bytes("base_to_arbitrum"))) { + _runSustainedBaseToArbitrumTest(); + } else { + revert("Invalid test direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'"); + } + + // Mark session as ended + currentSession.isActive = false; + + // Print final results + console2.log("=== SUSTAINED LOAD TEST COMPLETE ==="); + console2.log("Total transactions: %d", totalTransactions); + console2.log("Total successes: %d", totalSuccesses); + console2.log("Total failures: %d", totalFailures); + console2.log("Success rate: %d%%", totalTransactions > 0 ? (totalSuccesses * 100) / totalTransactions : 0); + console2.log("Total batches: %d", batchResults.length); + + emit SessionEnded(totalTransactions, totalSuccesses, totalFailures); + } + + function _runSustainedArbitrumToBaseTest() internal { + vm.createSelectFork(vm.rpcUrl("arbitrum_sepolia")); + + T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); + uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); + address alice = vm.addr(alicePk); + + // Approve tokens once at the beginning + vm.startBroadcast(alicePk); + ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); + inputToken.approve(address(l1_7683), type(uint256).max); + vm.stopBroadcast(); + + console2.log("Starting sustained Arbitrum -> Base load test..."); + + uint256 batchNumber = 0; + while (batchNumber < currentSession.totalBatches && currentSession.isActive) { + batchNumber++; + _executeBatchArbitrumToBase(l1_7683, alicePk, alice, batchNumber); + + // Wait for next interval (unless it's the last batch) + if (batchNumber < currentSession.totalBatches) { + uint256 waitTime = currentSession.intervalMinutes * 60; + console2.log("Waiting %d seconds for next batch...", currentSession.intervalMinutes * 60); + vm.sleep(waitTime * 1000); // Convert to milliseconds + } + } + } + + function _runSustainedBaseToArbitrumTest() internal { + vm.createSelectFork(vm.rpcUrl("base_sepolia")); + + T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); + uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); + address alice = vm.addr(alicePk); + + // Approve tokens once at the beginning + vm.startBroadcast(alicePk); + ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); + inputToken.approve(address(l1_7683), type(uint256).max); + vm.stopBroadcast(); + + console2.log("Starting sustained Base -> Arbitrum load test..."); + + uint256 batchNumber = 0; + while (batchNumber < currentSession.totalBatches && currentSession.isActive) { + batchNumber++; + _executeBatchBaseToArbitrum(l1_7683, alicePk, alice, batchNumber); + + // Wait for next interval (unless it's the last batch) + if (batchNumber < currentSession.totalBatches) { + uint256 waitTime = currentSession.intervalMinutes * 60; + console2.log("Waiting %d seconds for next batch...", currentSession.intervalMinutes * 60); + vm.sleep(waitTime * 1000); // Convert to milliseconds + } + } + } + + function _executeBatchArbitrumToBase( + T1ERC7683 l1_7683, + uint256 alicePk, + address alice, + uint256 batchNumber + ) internal { + console2.log("=== Executing Batch %d ===", batchNumber); + + bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); + uint256 batchSuccesses = 0; + uint256 batchFailures = 0; + + for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { + vm.startBroadcast(alicePk); + + ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); + ERC20 outputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); + + // Prepare order data with unique nonce + OrderData memory orderData = OrderData({ + sender: TypeCasts.addressToBytes32(alice), + recipient: TypeCasts.addressToBytes32(alice), + inputToken: TypeCasts.addressToBytes32(address(inputToken)), + outputToken: TypeCasts.addressToBytes32(address(outputToken)), + amountIn: AMOUNT_IN, + minAmountOut: AMOUNT_IN * 9 / 10, + senderNonce: uint32(nonceCounter), + originDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), + destinationDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), + destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")), + fillDeadline: uint32(1800), // 30 minutes from now + closedAuction: true, + data: new bytes(0) + }); + + bytes memory encodedOrder = OrderEncoder.encode(orderData); + + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + + l1_7683.open(order); + + bytes32 id = OrderEncoder.id(orderData); + + orderIds[i] = id; + batchSuccesses++; + totalSuccesses++; + emit TransactionSent(id, batchNumber, true); + + vm.stopBroadcast(); + + nonceCounter++; + totalTransactions++; + + // Small delay between transactions in the same batch + vm.sleep(100); // 100ms delay + } + + // Record batch results + batchResults.push(BatchResult({ + batchNumber: batchNumber, + timestamp: 0, // Not used in load test + successCount: batchSuccesses, + failureCount: batchFailures, + orderIds: orderIds + })); + + console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); + emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); + } + + function _executeBatchBaseToArbitrum( + T1ERC7683 l1_7683, + uint256 alicePk, + address alice, + uint256 batchNumber + ) internal { + console2.log("=== Executing Batch %d ===", batchNumber); + + bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); + uint256 batchSuccesses = 0; + uint256 batchFailures = 0; + + for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { + vm.startBroadcast(alicePk); + + ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); + ERC20 outputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); + + // Prepare order data with unique nonce + OrderData memory orderData = OrderData({ + sender: TypeCasts.addressToBytes32(alice), + recipient: TypeCasts.addressToBytes32(alice), + inputToken: TypeCasts.addressToBytes32(address(inputToken)), + outputToken: TypeCasts.addressToBytes32(address(outputToken)), + amountIn: AMOUNT_IN, + minAmountOut: AMOUNT_IN * 9 / 10, + senderNonce: uint32(nonceCounter), + originDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), + destinationDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), + destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")), + fillDeadline: uint32(1800), // 30 minutes from now + closedAuction: true, + data: new bytes(0) + }); + + bytes memory encodedOrder = OrderEncoder.encode(orderData); + + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + + l1_7683.open(order); + + bytes32 id = OrderEncoder.id(orderData); + + orderIds[i] = id; + batchSuccesses++; + totalSuccesses++; + emit TransactionSent(id, batchNumber, true); + + vm.stopBroadcast(); + + nonceCounter++; + totalTransactions++; + + // Small delay between transactions in the same batch + vm.sleep(100); // 100ms delay + } + + // Record batch results + batchResults.push(BatchResult({ + batchNumber: batchNumber, + timestamp: 0, // Not used in load test + successCount: batchSuccesses, + failureCount: batchFailures, + orderIds: orderIds + })); + + console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); + emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); + } + + function _prepareOnchainOrder( + bytes memory orderData, + uint32 fillDeadline, + bytes32 orderDataType + ) + internal + pure + returns (OnchainCrossChainOrder memory) + { + return + OnchainCrossChainOrder({ fillDeadline: fillDeadline, orderDataType: orderDataType, orderData: orderData }); + } + + // Utility functions for analysis + function getBatchResults() external view returns (BatchResult[] memory) { + return batchResults; + } + + function getCurrentSession() external view returns (TestSession memory) { + return currentSession; + } + + function getOverallSuccessRate() external view returns (uint256) { + if (totalTransactions == 0) return 0; + return (totalSuccesses * 100) / totalTransactions; + } + + function getAverageBatchSuccessRate() external view returns (uint256) { + if (batchResults.length == 0) return 0; + + uint256 totalBatchSuccesses = 0; + uint256 totalBatchTransactions = 0; + + for (uint256 i = 0; i < batchResults.length; i++) { + totalBatchSuccesses += batchResults[i].successCount; + totalBatchTransactions += batchResults[i].successCount + batchResults[i].failureCount; + } + + if (totalBatchTransactions == 0) return 0; + return (totalBatchSuccesses * 100) / totalBatchTransactions; + } + + // Emergency stop function + function emergencyStop() external { + require(currentSession.isActive, "No active session to stop"); + currentSession.isActive = false; + console2.log("Emergency stop triggered - session ended"); + } +} \ No newline at end of file diff --git a/contracts/script/test/7683/run-load-test.sh b/contracts/script/test/7683/run-load-test.sh new file mode 100755 index 00000000..1bba04f9 --- /dev/null +++ b/contracts/script/test/7683/run-load-test.sh @@ -0,0 +1,243 @@ +#!/bin/bash + +# 7683 Load Test Runner Script +# Usage: +# Burst Mode: ./run-load-test.sh burst [max_transactions] [direction] +# Sustained Mode: ./run-load-test.sh sustained [txs_per_batch] [interval] [duration] [direction] +# +# Examples: +# ./run-load-test.sh burst 50 arbitrum_to_base +# ./run-load-test.sh sustained 5 2m 20m base_to_arbitrum +# ./run-load-test.sh sustained 3 30s 5m arbitrum_to_base +# ./run-load-test.sh burst 25 # defaults to arbitrum_to_base + +# Load environment variables from .env file +load_env() { + # Look for .env file in current directory, then parent directories + local env_file="" + local current_dir="$(pwd)" + + # Check current directory first + if [ -f ".env" ]; then + env_file=".env" + # Check contracts directory (if running from script directory) + elif [ -f "../.env" ]; then + env_file="../.env" + # Check root directory (if running from contracts/script/test/7683) + elif [ -f "../../../.env" ]; then + env_file="../../../.env" + fi + + if [ -n "$env_file" ]; then + echo "Loading environment variables from: $env_file" + # Export variables from .env file, ignoring comments and empty lines + set -a # automatically export all variables + source "$env_file" + set +a # stop automatically exporting + echo "Environment variables loaded successfully" + else + echo "Warning: No .env file found in current directory or parent directories" + echo "Make sure you have a .env file with required variables" + fi +} + +# Function to parse time units (e.g., "30s", "2m", "1h") +parse_time() { + local time_str="$1" + local time_value="${time_str%[smh]}" + local time_unit="${time_str: -1}" + + case "$time_unit" in + s|S) + echo "$time_value" + ;; + m|M) + echo $((time_value * 60)) + ;; + h|H) + echo $((time_value * 3600)) + ;; + *) + # If no unit specified, assume seconds + echo "$time_value" + ;; + esac +} + +# Function to display usage +show_usage() { + echo "Usage:" + echo " Burst Mode: $0 burst [max_transactions] [direction]" + echo " Sustained Mode: $0 sustained [txs_per_batch] [interval] [duration] [direction]" + echo "" + echo "Examples:" + echo " $0 burst 50 arbitrum_to_base" + echo " $0 sustained 5 2m 20m base_to_arbitrum" + echo " $0 sustained 3 30s 5m arbitrum_to_base" + echo " $0 sustained 10 1h 2h arbitrum_to_base" + echo " $0 burst 25 # defaults to arbitrum_to_base" + echo "" + echo "Time units:" + echo " s = seconds (e.g., 30s)" + echo " m = minutes (e.g., 2m)" + echo " h = hours (e.g., 1h)" + echo "" + echo "Directions: arbitrum_to_base, base_to_arbitrum" +} + +# Load environment variables first +load_env + +# Check if at least one argument is provided +if [ $# -lt 1 ]; then + echo "Error: Missing test mode argument" + show_usage + exit 1 +fi + +MODE=$1 + +# Validate mode +if [[ "$MODE" != "burst" && "$MODE" != "sustained" ]]; then + echo "Error: Invalid mode. Use 'burst' or 'sustained'" + show_usage + exit 1 +fi + +# Set default RPC URL based on direction (will be overridden if direction is provided) +DEFAULT_RPC="arbitrum_sepolia" + +if [ "$MODE" = "burst" ]; then + # Burst mode configuration + MAX_TRANSACTIONS=${2:-10} + DIRECTION=${3:-arbitrum_to_base} + + # Validate direction + if [[ "$DIRECTION" != "arbitrum_to_base" && "$DIRECTION" != "base_to_arbitrum" ]]; then + echo "Error: Invalid direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'" + exit 1 + fi + + # Validate max transactions + if ! [[ "$MAX_TRANSACTIONS" =~ ^[0-9]+$ ]] || [ "$MAX_TRANSACTIONS" -lt 1 ]; then + echo "Error: Max transactions must be a positive integer" + exit 1 + fi + + # Set RPC URL based on direction + if [[ "$DIRECTION" == "arbitrum_to_base" ]]; then + RPC_URL="arbitrum_sepolia" + VERIFIER_URL="https://api-sepolia.arbiscan.io/api" + API_KEY="$ARBISCAN_API_KEY" + else + RPC_URL="base_sepolia" + VERIFIER_URL="https://api-sepolia.basescan.org/api" + API_KEY="$BASESCAN_API_KEY" + fi + + echo "==========================================" + echo "Starting 7683 BURST Load Test" + echo "==========================================" + echo "Mode: Burst" + echo "Max Transactions: $MAX_TRANSACTIONS" + echo "Direction: $DIRECTION" + echo "RPC URL: $RPC_URL" + echo "Timestamp: $(date)" + echo "==========================================" + + # Set environment variables for the test + export LOAD_TEST_MAX_TRANSACTIONS=$MAX_TRANSACTIONS + export LOAD_TEST_DIRECTION=$DIRECTION + + SCRIPT_NAME="BurstLoadTest" + +elif [ "$MODE" = "sustained" ]; then + # Sustained mode configuration + TXS_PER_BATCH=${2:-5} + INTERVAL_STR=${3:-1m} + DURATION_STR=${4:-10m} + DIRECTION=${5:-arbitrum_to_base} + + # Parse time strings to seconds + INTERVAL_SECONDS=$(parse_time "$INTERVAL_STR") + DURATION_SECONDS=$(parse_time "$DURATION_STR") + + # Validate direction + if [[ "$DIRECTION" != "arbitrum_to_base" && "$DIRECTION" != "base_to_arbitrum" ]]; then + echo "Error: Invalid direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'" + exit 1 + fi + + # Validate numeric inputs + if ! [[ "$TXS_PER_BATCH" =~ ^[0-9]+$ ]] || [ "$TXS_PER_BATCH" -lt 1 ]; then + echo "Error: Transactions per batch must be a positive integer" + exit 1 + fi + + if ! [[ "$INTERVAL_SECONDS" =~ ^[0-9]+$ ]] || [ "$INTERVAL_SECONDS" -lt 1 ]; then + echo "Error: Invalid interval format. Use formats like '30s', '2m', '1h'" + exit 1 + fi + + if ! [[ "$DURATION_SECONDS" =~ ^[0-9]+$ ]] || [ "$DURATION_SECONDS" -lt 1 ]; then + echo "Error: Invalid duration format. Use formats like '30s', '2m', '1h'" + exit 1 + fi + + # Set RPC URL based on direction + if [[ "$DIRECTION" == "arbitrum_to_base" ]]; then + RPC_URL="arbitrum_sepolia" + VERIFIER_URL="https://api-sepolia.arbiscan.io/api" + API_KEY="$ARBISCAN_API_KEY" + else + RPC_URL="base_sepolia" + VERIFIER_URL="https://api-sepolia.basescan.org/api" + API_KEY="$BASESCAN_API_KEY" + fi + + echo "==========================================" + echo "Starting 7683 SUSTAINED Load Test" + echo "==========================================" + echo "Mode: Sustained" + echo "Transactions per batch: $TXS_PER_BATCH" + echo "Interval: $INTERVAL_STR ($INTERVAL_SECONDS seconds)" + echo "Duration: $DURATION_STR ($DURATION_SECONDS seconds)" + echo "Direction: $DIRECTION" + echo "RPC URL: $RPC_URL" + echo "Timestamp: $(date)" + echo "==========================================" + + # Set environment variables for the test + export SUSTAINED_TXS_PER_BATCH=$TXS_PER_BATCH + export SUSTAINED_INTERVAL_SECONDS=$INTERVAL_SECONDS + export SUSTAINED_DURATION_SECONDS=$DURATION_SECONDS + export LOAD_TEST_DIRECTION=$DIRECTION + + SCRIPT_NAME="SustainedLoadTest" +fi + +# Check if required API keys are set +if [ -z "$API_KEY" ]; then + echo "Error: Required API key is not set in environment" + if [[ "$DIRECTION" == "arbitrum_to_base" ]]; then + echo "Please set ARBISCAN_API_KEY environment variable" + else + echo "Please set BASESCAN_API_KEY environment variable" + fi + exit 1 +fi + +# Run the Forge script +cd "$(dirname "$0")/../../.." +forge script ./script/test/7683/${SCRIPT_NAME}.s.sol:${SCRIPT_NAME} \ + --rpc-url $RPC_URL \ + --broadcast \ + --verify \ + --verifier etherscan \ + --verifier-url $VERIFIER_URL \ + --etherscan-api-key $API_KEY \ + -vvv + +echo "==========================================" +echo "Load test completed at: $(date)" +echo "==========================================" From f357f5d2ba5658ca19090da46574bbb8f2ce6f24 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Fri, 3 Oct 2025 11:58:27 +0200 Subject: [PATCH 02/14] fix: nonce issues in burst resolved --- .../script/test/7683/BurstLoadTest.s.sol | 109 +++++---- .../script/test/7683/SustainedLoadTest.s.sol | 229 +++++++++++------- 2 files changed, 214 insertions(+), 124 deletions(-) diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol index 4cea3ab7..a2f66175 100644 --- a/contracts/script/test/7683/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/BurstLoadTest.s.sol @@ -119,14 +119,20 @@ contract BurstLoadTest is Script { T1ERC7683 l1_7683, uint256 alicePk, address alice, - uint256 nonce + uint256 transactionIndex // Renamed for clarity ) internal { vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); ERC20 outputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - // Prepare order data with unique nonce + // Find the next valid nonce + uint32 nextNonce = 0; + while (l1_7683.usedNonces(alice, nextNonce) || !l1_7683.isValidNonce(alice, nextNonce)) { + nextNonce++; + require(nextNonce < type(uint32).max, "No valid nonce available"); + } + OrderData memory orderData = OrderData({ sender: TypeCasts.addressToBytes32(alice), recipient: TypeCasts.addressToBytes32(alice), @@ -134,34 +140,41 @@ contract BurstLoadTest is Script { outputToken: TypeCasts.addressToBytes32(address(outputToken)), amountIn: AMOUNT_IN, minAmountOut: AMOUNT_IN * 9 / 10, - senderNonce: uint32(nonce), + senderNonce: nextNonce, originDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), destinationDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), // 30 minutes from now + fillDeadline: uint32(1800), closedAuction: true, data: new bytes(0) }); bytes memory encodedOrder = OrderEncoder.encode(orderData); + OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - l1_7683.open(order); - - bytes32 id = OrderEncoder.id(orderData); - - results.push(TestResult({ - orderId: id, - txHash: uint256(keccak256(abi.encodePacked(nonce, "burst_test"))), - timestamp: 0, // Not used in load test - success: true - })); - - emit TransactionSent(id, results[results.length - 1].txHash, true); - successCount++; - console2.log("Transaction", nonce + 1, "successful"); + // Call open with try-catch to handle reverts + try l1_7683.open(order) { + bytes32 id = OrderEncoder.id(orderData); + results.push(TestResult({ + orderId: id, + txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), + timestamp: 0, + success: true + })); + emit TransactionSent(id, results[results.length - 1].txHash, true); + successCount++; + console2.log("Transaction", transactionIndex + 1, "successful with nonce", nextNonce); + } catch Error(string memory reason) { + results.push(TestResult({ + orderId: OrderEncoder.id(orderData), + txHash: 0, + timestamp: 0, + success: false + })); + // console2.log("Transaction", transactionIndex + 1, "failed with nonce", nextNonce, "reason:", reason); + failureCount++; + emit TransactionSent(OrderEncoder.id(orderData), 0, false); + } vm.stopBroadcast(); } @@ -170,14 +183,20 @@ contract BurstLoadTest is Script { T1ERC7683 l1_7683, uint256 alicePk, address alice, - uint256 nonce + uint256 transactionIndex ) internal { vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); ERC20 outputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - // Prepare order data with unique nonce + // Find the next valid nonce + uint32 nextNonce = 0; + while (l1_7683.usedNonces(alice, nextNonce) || !l1_7683.isValidNonce(alice, nextNonce)) { + nextNonce++; + require(nextNonce < type(uint32).max, "No valid nonce available"); + } + OrderData memory orderData = OrderData({ sender: TypeCasts.addressToBytes32(alice), recipient: TypeCasts.addressToBytes32(alice), @@ -185,34 +204,40 @@ contract BurstLoadTest is Script { outputToken: TypeCasts.addressToBytes32(address(outputToken)), amountIn: AMOUNT_IN, minAmountOut: AMOUNT_IN * 9 / 10, - senderNonce: uint32(nonce), + senderNonce: nextNonce, originDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), destinationDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), // 30 minutes from now + fillDeadline: uint32(1800), closedAuction: true, data: new bytes(0) }); bytes memory encodedOrder = OrderEncoder.encode(orderData); + OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - l1_7683.open(order); - - bytes32 id = OrderEncoder.id(orderData); - - results.push(TestResult({ - orderId: id, - txHash: uint256(keccak256(abi.encodePacked(nonce, "burst_test"))), - timestamp: 0, // Not used in load test - success: true - })); - - emit TransactionSent(id, results[results.length - 1].txHash, true); - successCount++; - console2.log("Transaction", nonce + 1, "successful"); + try l1_7683.open(order) { + bytes32 id = OrderEncoder.id(orderData); + results.push(TestResult({ + orderId: id, + txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), + timestamp: 0, + success: true + })); + emit TransactionSent(id, results[results.length - 1].txHash, true); + successCount++; + console2.log("Transaction", transactionIndex + 1, "successful with nonce", nextNonce); + } catch Error(string memory reason) { + results.push(TestResult({ + orderId: OrderEncoder.id(orderData), + txHash: 0, + timestamp: 0, + success: false + })); + failureCount++; + // console2.log("Transaction", transactionIndex + 1, "failed with nonce", nextNonce, "reason:", reason); + emit TransactionSent(OrderEncoder.id(orderData), 0, false); + } vm.stopBroadcast(); } diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol index 724d68fd..e3a11a44 100644 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -12,8 +12,8 @@ import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; /** * @title SustainedLoadTest - * @dev Sustained load testing script for 7683 contracts that sends n transactions - * every t seconds for a configurable duration. Supports both Arbitrum->Base and Base->Arbitrum flows. + * @dev Sustained load testing script for 7683 contracts that sends n transactions + * every t seconds for a configurable duration. Supports both Arbitrum->Base and Base->Arbitrum flows. */ contract SustainedLoadTest is Script { // Configuration from environment variables @@ -21,7 +21,7 @@ contract SustainedLoadTest is Script { uint256 public constant DEFAULT_INTERVAL_SECONDS = 60; // 1 minute uint256 public constant DEFAULT_DURATION_SECONDS = 600; // 10 minutes uint256 public constant AMOUNT_IN = 10; // 0.00001 USDT (assuming 6 decimals) - + // Test state struct TestSession { uint256 startTime; @@ -31,7 +31,7 @@ contract SustainedLoadTest is Script { uint256 intervalMinutes; bool isActive; } - + struct BatchResult { uint256 batchNumber; uint256 timestamp; @@ -39,14 +39,14 @@ contract SustainedLoadTest is Script { uint256 failureCount; bytes32[] orderIds; } - + TestSession public currentSession; BatchResult[] public batchResults; uint256 public totalTransactions; uint256 public totalSuccesses; uint256 public totalFailures; uint256 public nonceCounter; - + event SessionStarted(uint256 startTime, uint256 endTime, uint256 transactionsPerBatch, uint256 intervalSeconds); event BatchCompleted(uint256 batchNumber, uint256 successCount, uint256 failureCount); event SessionEnded(uint256 totalTransactions, uint256 totalSuccesses, uint256 totalFailures); @@ -58,18 +58,23 @@ contract SustainedLoadTest is Script { uint256 intervalSeconds = vm.envOr("SUSTAINED_INTERVAL_SECONDS", uint256(DEFAULT_INTERVAL_SECONDS)); uint256 durationSeconds = vm.envOr("SUSTAINED_DURATION_SECONDS", uint256(DEFAULT_DURATION_SECONDS)); string memory testDirection = vm.envOr("LOAD_TEST_DIRECTION", string("arbitrum_to_base")); - + console2.log("=== STARTING SUSTAINED LOAD TEST ==="); console2.log("Transactions per batch: %d", txsPerBatch); console2.log("Interval (seconds): %d", intervalSeconds); console2.log("Duration (seconds): %d", durationSeconds); console2.log("Test direction: %s", testDirection); - + console2.log("ARB_T1_PULL_BASED_7683_PROXY_ADDR: %s", vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); + console2.log("BASE_T1_PULL_BASED_7683_PROXY_ADDR: %s", vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); + console2.log("ARBITRUM_SEPOLIA_USDT_ADDR: %s", vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); + console2.log("BASE_SEPOLIA_USDT_ADDR: %s", vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); + console2.log("ALICE_ADDRESS: %s", vm.addr(vm.envUint("ALICE_PRIVATE_KEY"))); + // Validate configuration require(txsPerBatch > 0, "Transactions per batch must be > 0"); require(intervalSeconds > 0, "Interval must be > 0"); require(durationSeconds > 0, "Duration must be > 0"); - + // Initialize session currentSession = TestSession({ startTime: 0, // Not used for timing in script @@ -79,16 +84,16 @@ contract SustainedLoadTest is Script { intervalMinutes: intervalSeconds / 60, // Convert to minutes for compatibility isActive: true }); - + // Reset counters delete batchResults; totalTransactions = 0; totalSuccesses = 0; totalFailures = 0; nonceCounter = 0; - + emit SessionStarted(currentSession.startTime, currentSession.endTime, txsPerBatch, intervalSeconds); - + // Execute sustained load test based on direction if (keccak256(bytes(testDirection)) == keccak256(bytes("arbitrum_to_base"))) { _runSustainedArbitrumToBaseTest(); @@ -97,10 +102,10 @@ contract SustainedLoadTest is Script { } else { revert("Invalid test direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'"); } - + // Mark session as ended currentSession.isActive = false; - + // Print final results console2.log("=== SUSTAINED LOAD TEST COMPLETE ==="); console2.log("Total transactions: %d", totalTransactions); @@ -108,34 +113,31 @@ contract SustainedLoadTest is Script { console2.log("Total failures: %d", totalFailures); console2.log("Success rate: %d%%", totalTransactions > 0 ? (totalSuccesses * 100) / totalTransactions : 0); console2.log("Total batches: %d", batchResults.length); - emit SessionEnded(totalTransactions, totalSuccesses, totalFailures); } function _runSustainedArbitrumToBaseTest() internal { vm.createSelectFork(vm.rpcUrl("arbitrum_sepolia")); - T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); - + // Approve tokens once at the beginning vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); - - console2.log("Starting sustained Arbitrum -> Base load test..."); - + + console2.log("Starting sustained Arbitrum -> Base load test with initial nonce: %d", nonceCounter); + uint256 batchNumber = 0; while (batchNumber < currentSession.totalBatches && currentSession.isActive) { batchNumber++; _executeBatchArbitrumToBase(l1_7683, alicePk, alice, batchNumber); - // Wait for next interval (unless it's the last batch) if (batchNumber < currentSession.totalBatches) { uint256 waitTime = currentSession.intervalMinutes * 60; - console2.log("Waiting %d seconds for next batch...", currentSession.intervalMinutes * 60); + console2.log("Waiting %d seconds for next batch...", waitTime); vm.sleep(waitTime * 1000); // Convert to milliseconds } } @@ -143,28 +145,26 @@ contract SustainedLoadTest is Script { function _runSustainedBaseToArbitrumTest() internal { vm.createSelectFork(vm.rpcUrl("base_sepolia")); - T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); - + // Approve tokens once at the beginning vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); - - console2.log("Starting sustained Base -> Arbitrum load test..."); - + + console2.log("Starting sustained Base -> Arbitrum load test with initial nonce: %d", nonceCounter); + uint256 batchNumber = 0; while (batchNumber < currentSession.totalBatches && currentSession.isActive) { batchNumber++; _executeBatchBaseToArbitrum(l1_7683, alicePk, alice, batchNumber); - // Wait for next interval (unless it's the last batch) if (batchNumber < currentSession.totalBatches) { uint256 waitTime = currentSession.intervalMinutes * 60; - console2.log("Waiting %d seconds for next batch...", currentSession.intervalMinutes * 60); + console2.log("Waiting %d seconds for next batch...", waitTime); vm.sleep(waitTime * 1000); // Convert to milliseconds } } @@ -177,18 +177,39 @@ contract SustainedLoadTest is Script { uint256 batchNumber ) internal { console2.log("=== Executing Batch %d ===", batchNumber); - bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); uint256 batchSuccesses = 0; uint256 batchFailures = 0; - + for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { - vm.startBroadcast(alicePk); + // Find a valid (unused) nonce + uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop + uint256 attempts = 0; + while (!l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { + nonceCounter++; + attempts++; + } + if (attempts >= maxAttempts) { + console2.log("Transaction %d failed: Could not find valid nonce", i); + batchFailures++; + totalFailures++; + continue; + } + vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); ERC20 outputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - // Prepare order data with unique nonce + // Log allowance and balance + uint256 allowance = inputToken.allowance(alice, address(l1_7683)); + uint256 balance = inputToken.balanceOf(alice); + console2.log("Transaction %d: Allowance: %d, Balance: %d", i, allowance, balance); + + // Log Ethereum nonce + uint256 ethNonce = vm.getNonce(alice); + console2.log("Transaction %d: Ethereum nonce: %d, SenderNonce: %d", i, ethNonce, nonceCounter); + + // Prepare order data OrderData memory orderData = OrderData({ sender: TypeCasts.addressToBytes32(alice), recipient: TypeCasts.addressToBytes32(alice), @@ -200,43 +221,52 @@ contract SustainedLoadTest is Script { originDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), destinationDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), // 30 minutes from now + fillDeadline: uint32(1800), // 30 minutes closedAuction: true, data: new bytes(0) }); bytes memory encodedOrder = OrderEncoder.encode(orderData); + OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - l1_7683.open(order); - - bytes32 id = OrderEncoder.id(orderData); - - orderIds[i] = id; - batchSuccesses++; - totalSuccesses++; - emit TransactionSent(id, batchNumber, true); + // Try submitting the order + try l1_7683.open(order) { + bytes32 id = OrderEncoder.id(orderData); + orderIds[i] = id; + batchSuccesses++; + totalSuccesses++; + console2.log("Transaction %d succeeded, Order ID: %s", i, vm.toString(id)); + emit TransactionSent(id, batchNumber, true); + nonceCounter++; // Increment nonce only on success + } catch Error(string memory reason) { + console2.log("Transaction %d failed: %s", i, reason); + batchFailures++; + totalFailures++; + orderIds[i] = bytes32(0); + emit TransactionSent(bytes32(0), batchNumber, false); + } catch { + console2.log("Transaction %d failed with unknown error", i); + batchFailures++; + totalFailures++; + orderIds[i] = bytes32(0); + emit TransactionSent(bytes32(0), batchNumber, false); + } vm.stopBroadcast(); - - nonceCounter++; totalTransactions++; - - // Small delay between transactions in the same batch - vm.sleep(100); // 100ms delay + + // Wait for transaction confirmation or add delay + vm.sleep(500); // 500ms delay } - + // Record batch results batchResults.push(BatchResult({ batchNumber: batchNumber, - timestamp: 0, // Not used in load test + timestamp: 0, successCount: batchSuccesses, failureCount: batchFailures, orderIds: orderIds })); - console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); } @@ -248,18 +278,39 @@ contract SustainedLoadTest is Script { uint256 batchNumber ) internal { console2.log("=== Executing Batch %d ===", batchNumber); - bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); uint256 batchSuccesses = 0; uint256 batchFailures = 0; - + for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { - vm.startBroadcast(alicePk); + // Find a valid (unused) nonce + uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop + uint256 attempts = 0; + while (!l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { + nonceCounter++; + attempts++; + } + if (attempts >= maxAttempts) { + console2.log("Transaction %d failed: Could not find valid nonce", i); + batchFailures++; + totalFailures++; + continue; + } + vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); ERC20 outputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - // Prepare order data with unique nonce + // Log allowance and balance + uint256 allowance = inputToken.allowance(alice, address(l1_7683)); + uint256 balance = inputToken.balanceOf(alice); + console2.log("Transaction %d: Allowance: %d, Balance: %d", i, allowance, balance); + + // Log Ethereum nonce + uint256 ethNonce = vm.getNonce(alice); + console2.log("Transaction %d: Ethereum nonce: %d, SenderNonce: %d", i, ethNonce, nonceCounter); + + // Prepare order data OrderData memory orderData = OrderData({ sender: TypeCasts.addressToBytes32(alice), recipient: TypeCasts.addressToBytes32(alice), @@ -271,43 +322,52 @@ contract SustainedLoadTest is Script { originDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), destinationDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), // 30 minutes from now + fillDeadline: uint32(1800), // 30 minutes closedAuction: true, data: new bytes(0) }); bytes memory encodedOrder = OrderEncoder.encode(orderData); + OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - l1_7683.open(order); - - bytes32 id = OrderEncoder.id(orderData); - - orderIds[i] = id; - batchSuccesses++; - totalSuccesses++; - emit TransactionSent(id, batchNumber, true); + // Try submitting the order + try l1_7683.open(order) { + bytes32 id = OrderEncoder.id(orderData); + orderIds[i] = id; + batchSuccesses++; + totalSuccesses++; + console2.log("Transaction %d succeeded, Order ID: %s", i, vm.toString(id)); + emit TransactionSent(id, batchNumber, true); + nonceCounter++; // Increment nonce only on success + } catch Error(string memory reason) { + console2.log("Transaction %d failed: %s", i, reason); + batchFailures++; + totalFailures++; + orderIds[i] = bytes32(0); + emit TransactionSent(bytes32(0), batchNumber, false); + } catch { + console2.log("Transaction %d failed with unknown error", i); + batchFailures++; + totalFailures++; + orderIds[i] = bytes32(0); + emit TransactionSent(bytes32(0), batchNumber, false); + } vm.stopBroadcast(); - - nonceCounter++; totalTransactions++; - - // Small delay between transactions in the same batch - vm.sleep(100); // 100ms delay + + // Wait for transaction confirmation or add delay + vm.sleep(500); // 500ms delay } - + // Record batch results batchResults.push(BatchResult({ batchNumber: batchNumber, - timestamp: 0, // Not used in load test + timestamp: 0, successCount: batchSuccesses, failureCount: batchFailures, orderIds: orderIds })); - console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); } @@ -321,8 +381,11 @@ contract SustainedLoadTest is Script { pure returns (OnchainCrossChainOrder memory) { - return - OnchainCrossChainOrder({ fillDeadline: fillDeadline, orderDataType: orderDataType, orderData: orderData }); + return OnchainCrossChainOrder({ + fillDeadline: fillDeadline, + orderDataType: orderDataType, + orderData: orderData + }); } // Utility functions for analysis @@ -341,15 +404,12 @@ contract SustainedLoadTest is Script { function getAverageBatchSuccessRate() external view returns (uint256) { if (batchResults.length == 0) return 0; - uint256 totalBatchSuccesses = 0; uint256 totalBatchTransactions = 0; - for (uint256 i = 0; i < batchResults.length; i++) { totalBatchSuccesses += batchResults[i].successCount; totalBatchTransactions += batchResults[i].successCount + batchResults[i].failureCount; } - if (totalBatchTransactions == 0) return 0; return (totalBatchSuccesses * 100) / totalBatchTransactions; } @@ -360,4 +420,9 @@ contract SustainedLoadTest is Script { currentSession.isActive = false; console2.log("Emergency stop triggered - session ended"); } +} + +interface T1ERC7683 { + function open(OnchainCrossChainOrder memory order) external payable; + function isValidNonce(address _from, uint256 _nonce) external view returns (bool); } \ No newline at end of file From 20a25da4ce2947c3cb9488cf94d53d2818954a3e Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Fri, 3 Oct 2025 12:23:23 +0200 Subject: [PATCH 03/14] fix: nonce issues in sustained test --- .../script/test/7683/SustainedLoadTest.s.sol | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol index e3a11a44..f0924800 100644 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -128,6 +128,13 @@ contract SustainedLoadTest is Script { inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); + // Debug nonce state before starting + console2.log("Checking used nonces for %s on Arbitrum", alice); + for (uint32 i = 0; i < 10; i++) { + console2.log("Nonce %d used: %s", i, l1_7683.usedNonces(alice, i)); + console2.log("Nonce %d valid: %s", i, l1_7683.isValidNonce(alice, i)); + } + console2.log("Starting sustained Arbitrum -> Base load test with initial nonce: %d", nonceCounter); uint256 batchNumber = 0; @@ -155,6 +162,13 @@ contract SustainedLoadTest is Script { inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); + // Debug nonce state before starting + console2.log("Checking used nonces for %s on Base", alice); + for (uint32 i = 0; i < 10; i++) { + console2.log("Nonce %d used: %s", i, l1_7683.usedNonces(alice, i)); + console2.log("Nonce %d valid: %s", i, l1_7683.isValidNonce(alice, i)); + } + console2.log("Starting sustained Base -> Arbitrum load test with initial nonce: %d", nonceCounter); uint256 batchNumber = 0; @@ -185,7 +199,7 @@ contract SustainedLoadTest is Script { // Find a valid (unused) nonce uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop uint256 attempts = 0; - while (!l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { + while (l1_7683.usedNonces(alice, nonceCounter) || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { nonceCounter++; attempts++; } @@ -235,17 +249,17 @@ contract SustainedLoadTest is Script { orderIds[i] = id; batchSuccesses++; totalSuccesses++; - console2.log("Transaction %d succeeded, Order ID: %s", i, vm.toString(id)); + console2.log("Transaction %d succeeded with nonce %d, Order ID: %s", i, nonceCounter, vm.toString(id)); emit TransactionSent(id, batchNumber, true); nonceCounter++; // Increment nonce only on success } catch Error(string memory reason) { - console2.log("Transaction %d failed: %s", i, reason); + console2.log("Transaction %d failed with nonce %d: %s", i, nonceCounter, reason); batchFailures++; totalFailures++; orderIds[i] = bytes32(0); emit TransactionSent(bytes32(0), batchNumber, false); } catch { - console2.log("Transaction %d failed with unknown error", i); + console2.log("Transaction %d failed with nonce %d with unknown error", i, nonceCounter); batchFailures++; totalFailures++; orderIds[i] = bytes32(0); @@ -286,7 +300,7 @@ contract SustainedLoadTest is Script { // Find a valid (unused) nonce uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop uint256 attempts = 0; - while (!l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { + while (l1_7683.usedNonces(alice, nonceCounter) || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { nonceCounter++; attempts++; } @@ -336,17 +350,17 @@ contract SustainedLoadTest is Script { orderIds[i] = id; batchSuccesses++; totalSuccesses++; - console2.log("Transaction %d succeeded, Order ID: %s", i, vm.toString(id)); + console2.log("Transaction %d succeeded with nonce %d, Order ID: %s", i, nonceCounter, vm.toString(id)); emit TransactionSent(id, batchNumber, true); nonceCounter++; // Increment nonce only on success } catch Error(string memory reason) { - console2.log("Transaction %d failed: %s", i, reason); + console2.log("Transaction %d failed with nonce %d: %s", i, nonceCounter, reason); batchFailures++; totalFailures++; orderIds[i] = bytes32(0); emit TransactionSent(bytes32(0), batchNumber, false); } catch { - console2.log("Transaction %d failed with unknown error", i); + console2.log("Transaction %d failed with nonce %d with unknown error", i, nonceCounter); batchFailures++; totalFailures++; orderIds[i] = bytes32(0); @@ -421,8 +435,3 @@ contract SustainedLoadTest is Script { console2.log("Emergency stop triggered - session ended"); } } - -interface T1ERC7683 { - function open(OnchainCrossChainOrder memory order) external payable; - function isValidNonce(address _from, uint256 _nonce) external view returns (bool); -} \ No newline at end of file From 141dc53bf1d963d51ce02633c85f83ae530536e8 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Fri, 3 Oct 2025 12:26:46 +0200 Subject: [PATCH 04/14] fix: forge fmt --- .../script/test/7683/BurstLoadTest.s.sol | 94 +++++++++---------- .../script/test/7683/SustainedLoadTest.s.sol | 63 ++++++++----- 2 files changed, 85 insertions(+), 72 deletions(-) diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol index a2f66175..849628d4 100644 --- a/contracts/script/test/7683/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/BurstLoadTest.s.sol @@ -19,7 +19,7 @@ contract BurstLoadTest is Script { // Configuration from environment variables uint256 public constant MAX_TRANSACTIONS = 10; // Default, can be overridden by env var uint256 public constant AMOUNT_IN = 10; // 0.00001 USDT (assuming 6 decimals) - + // Test results tracking struct TestResult { bytes32 orderId; @@ -27,11 +27,11 @@ contract BurstLoadTest is Script { uint256 timestamp; bool success; } - + TestResult[] public results; uint256 public successCount; uint256 public failureCount; - + event TransactionSent(bytes32 indexed orderId, uint256 txHash, bool success); event LoadTestComplete(uint256 totalTransactions, uint256 successCount, uint256 failureCount); @@ -39,15 +39,15 @@ contract BurstLoadTest is Script { // Get configuration from environment uint256 maxTxs = vm.envOr("LOAD_TEST_MAX_TRANSACTIONS", uint256(MAX_TRANSACTIONS)); string memory testDirection = vm.envOr("LOAD_TEST_DIRECTION", string("arbitrum_to_base")); - + console2.log("Starting load test with", maxTxs, "transactions"); console2.log("Test direction:", testDirection); - + // Reset counters delete results; successCount = 0; failureCount = 0; - + // Execute load test based on direction if (keccak256(bytes(testDirection)) == keccak256(bytes("arbitrum_to_base"))) { _runArbitrumToBaseLoadTest(maxTxs); @@ -56,36 +56,36 @@ contract BurstLoadTest is Script { } else { revert("Invalid test direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'"); } - + // Print final results console2.log("=== LOAD TEST COMPLETE ==="); console2.log("Total transactions:", maxTxs); console2.log("Successful:", successCount); console2.log("Failed:", failureCount); console2.log("Success rate:", (successCount * 100) / maxTxs, "%"); - + emit LoadTestComplete(maxTxs, successCount, failureCount); } function _runArbitrumToBaseLoadTest(uint256 maxTxs) internal { vm.createSelectFork(vm.rpcUrl("arbitrum_sepolia")); - + T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); - + // Approve tokens once at the beginning vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); - + console2.log("Starting Arbitrum -> Base load test..."); - + // Send transactions sequentially for (uint256 i = 0; i < maxTxs; i++) { _sendArbitrumToBaseTransaction(l1_7683, alicePk, alice, i); - + // Small delay to avoid overwhelming the RPC vm.sleep(100); // 100ms delay between transactions } @@ -93,23 +93,23 @@ contract BurstLoadTest is Script { function _runBaseToArbitrumLoadTest(uint256 maxTxs) internal { vm.createSelectFork(vm.rpcUrl("base_sepolia")); - + T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); - + // Approve tokens once at the beginning vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); - + console2.log("Starting Base -> Arbitrum load test..."); - + // Send transactions sequentially for (uint256 i = 0; i < maxTxs; i++) { _sendBaseToArbitrumTransaction(l1_7683, alicePk, alice, i); - + // Small delay to avoid overwhelming the RPC vm.sleep(100); // 100ms delay between transactions } @@ -120,7 +120,9 @@ contract BurstLoadTest is Script { uint256 alicePk, address alice, uint256 transactionIndex // Renamed for clarity - ) internal { + ) + internal + { vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); @@ -150,27 +152,25 @@ contract BurstLoadTest is Script { }); bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); // Call open with try-catch to handle reverts try l1_7683.open(order) { bytes32 id = OrderEncoder.id(orderData); - results.push(TestResult({ - orderId: id, - txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), - timestamp: 0, - success: true - })); + results.push( + TestResult({ + orderId: id, + txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), + timestamp: 0, + success: true + }) + ); emit TransactionSent(id, results[results.length - 1].txHash, true); successCount++; console2.log("Transaction", transactionIndex + 1, "successful with nonce", nextNonce); } catch Error(string memory reason) { - results.push(TestResult({ - orderId: OrderEncoder.id(orderData), - txHash: 0, - timestamp: 0, - success: false - })); + results.push(TestResult({ orderId: OrderEncoder.id(orderData), txHash: 0, timestamp: 0, success: false })); // console2.log("Transaction", transactionIndex + 1, "failed with nonce", nextNonce, "reason:", reason); failureCount++; emit TransactionSent(OrderEncoder.id(orderData), 0, false); @@ -184,7 +184,9 @@ contract BurstLoadTest is Script { uint256 alicePk, address alice, uint256 transactionIndex - ) internal { + ) + internal + { vm.startBroadcast(alicePk); ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); @@ -214,26 +216,24 @@ contract BurstLoadTest is Script { }); bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); try l1_7683.open(order) { bytes32 id = OrderEncoder.id(orderData); - results.push(TestResult({ - orderId: id, - txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), - timestamp: 0, - success: true - })); + results.push( + TestResult({ + orderId: id, + txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), + timestamp: 0, + success: true + }) + ); emit TransactionSent(id, results[results.length - 1].txHash, true); successCount++; console2.log("Transaction", transactionIndex + 1, "successful with nonce", nextNonce); } catch Error(string memory reason) { - results.push(TestResult({ - orderId: OrderEncoder.id(orderData), - txHash: 0, - timestamp: 0, - success: false - })); + results.push(TestResult({ orderId: OrderEncoder.id(orderData), txHash: 0, timestamp: 0, success: false })); failureCount++; // console2.log("Transaction", transactionIndex + 1, "failed with nonce", nextNonce, "reason:", reason); emit TransactionSent(OrderEncoder.id(orderData), 0, false); @@ -264,4 +264,4 @@ contract BurstLoadTest is Script { if (successCount + failureCount == 0) return 0; return (successCount * 100) / (successCount + failureCount); } -} \ No newline at end of file +} diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol index f0924800..9ae2e0f8 100644 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -189,7 +189,9 @@ contract SustainedLoadTest is Script { uint256 alicePk, address alice, uint256 batchNumber - ) internal { + ) + internal + { console2.log("=== Executing Batch %d ===", batchNumber); bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); uint256 batchSuccesses = 0; @@ -199,7 +201,10 @@ contract SustainedLoadTest is Script { // Find a valid (unused) nonce uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop uint256 attempts = 0; - while (l1_7683.usedNonces(alice, nonceCounter) || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { + while ( + l1_7683.usedNonces(alice, nonceCounter) + || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts + ) { nonceCounter++; attempts++; } @@ -241,7 +246,8 @@ contract SustainedLoadTest is Script { }); bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); // Try submitting the order try l1_7683.open(order) { @@ -274,13 +280,15 @@ contract SustainedLoadTest is Script { } // Record batch results - batchResults.push(BatchResult({ - batchNumber: batchNumber, - timestamp: 0, - successCount: batchSuccesses, - failureCount: batchFailures, - orderIds: orderIds - })); + batchResults.push( + BatchResult({ + batchNumber: batchNumber, + timestamp: 0, + successCount: batchSuccesses, + failureCount: batchFailures, + orderIds: orderIds + }) + ); console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); } @@ -290,7 +298,9 @@ contract SustainedLoadTest is Script { uint256 alicePk, address alice, uint256 batchNumber - ) internal { + ) + internal + { console2.log("=== Executing Batch %d ===", batchNumber); bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); uint256 batchSuccesses = 0; @@ -300,7 +310,10 @@ contract SustainedLoadTest is Script { // Find a valid (unused) nonce uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop uint256 attempts = 0; - while (l1_7683.usedNonces(alice, nonceCounter) || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts) { + while ( + l1_7683.usedNonces(alice, nonceCounter) + || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts + ) { nonceCounter++; attempts++; } @@ -342,7 +355,8 @@ contract SustainedLoadTest is Script { }); bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); + OnchainCrossChainOrder memory order = + _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); // Try submitting the order try l1_7683.open(order) { @@ -375,13 +389,15 @@ contract SustainedLoadTest is Script { } // Record batch results - batchResults.push(BatchResult({ - batchNumber: batchNumber, - timestamp: 0, - successCount: batchSuccesses, - failureCount: batchFailures, - orderIds: orderIds - })); + batchResults.push( + BatchResult({ + batchNumber: batchNumber, + timestamp: 0, + successCount: batchSuccesses, + failureCount: batchFailures, + orderIds: orderIds + }) + ); console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); } @@ -395,11 +411,8 @@ contract SustainedLoadTest is Script { pure returns (OnchainCrossChainOrder memory) { - return OnchainCrossChainOrder({ - fillDeadline: fillDeadline, - orderDataType: orderDataType, - orderData: orderData - }); + return + OnchainCrossChainOrder({ fillDeadline: fillDeadline, orderDataType: orderDataType, orderData: orderData }); } // Utility functions for analysis From 77ddf94cc8657b3ecd70ba109faedcc9dfd2b98c Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Fri, 3 Oct 2025 12:29:19 +0200 Subject: [PATCH 05/14] fix: prettier formatting for docs --- contracts/script/test/7683/README.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/contracts/script/test/7683/README.md b/contracts/script/test/7683/README.md index baf48e93..1313cb8a 100644 --- a/contracts/script/test/7683/README.md +++ b/contracts/script/test/7683/README.md @@ -5,17 +5,21 @@ This directory contains load testing scripts for the 7683 cross-chain contracts. ## Scripts ### BurstLoadTest.s.sol + Sends a configurable number of transactions sequentially as fast as possible for burst load testing. ### SustainedLoadTest.s.sol + Sends n transactions every t minutes for a sustained load test over a configurable duration. ### run-load-test.sh + Shell script runner that can execute either burst or sustained load tests with proper configuration. ## Usage ### Prerequisites + - Ensure your `.env` file is properly configured with all required variables - Make sure you have sufficient tokens for testing (USDT on both chains) - Set the required API keys: `ARBISCAN_API_KEY` and `BASESCAN_API_KEY` @@ -54,24 +58,28 @@ Send n transactions every t interval for a specified duration with flexible time ``` **Time Units:** + - `s` = seconds (e.g., `30s`) -- `m` = minutes (e.g., `2m`) +- `m` = minutes (e.g., `2m`) - `h` = hours (e.g., `1h`) ## Environment Variables ### Required for Both Modes + - `ALICE_PRIVATE_KEY` - Private key for the test account - `ARBISCAN_API_KEY` - API key for Arbitrum verification - `BASESCAN_API_KEY` - API key for Base verification ### Required Contract Addresses + - `ARB_T1_PULL_BASED_7683_PROXY_ADDR` - Arbitrum 7683 contract - `BASE_T1_PULL_BASED_7683_PROXY_ADDR` - Base 7683 contract - `ARBITRUM_SEPOLIA_USDT_ADDR` - USDT on Arbitrum Sepolia - `BASE_SEPOLIA_USDT_ADDR` - USDT on Base Sepolia ### Optional Configuration + - `LOAD_TEST_MAX_TRANSACTIONS` - Override default max transactions for burst mode - `SUSTAINED_TXS_PER_BATCH` - Override default transactions per batch for sustained mode - `SUSTAINED_INTERVAL_MINUTES` - Override default interval for sustained mode @@ -84,7 +92,7 @@ Both scripts follow the same basic flow: 1. **Setup**: Connect to the appropriate RPC and load contract addresses 2. **Token Approval**: Approve the maximum amount of USDT for the 7683 contract -3. **Transaction Execution**: +3. **Transaction Execution**: - Create cross-chain order data with unique nonces - Call `T1ERC7683.open()` with the order - Track success/failure rates @@ -93,12 +101,14 @@ Both scripts follow the same basic flow: ## Monitoring ### Events Emitted + - `TransactionSent(bytes32 orderId, uint256 batchNumber, bool success)` - `LoadTestComplete(uint256 totalTransactions, uint256 successCount, uint256 failureCount)` (burst mode) - `BatchCompleted(uint256 batchNumber, uint256 successCount, uint256 failureCount)` (sustained mode) - `SessionStarted/Ended` events (sustained mode) ### Console Output + - Real-time progress updates - Success/failure counts - Final statistics including success rates @@ -113,35 +123,42 @@ Both scripts follow the same basic flow: ## Emergency Controls ### Sustained Mode Emergency Stop -The sustained load test includes an `emergencyStop()` function that can be called to immediately halt an active test session. + +The sustained load test includes an `emergencyStop()` function that can be called to immediately halt an active test +session. ## Example Test Scenarios ### Quick Burst Test + ```bash # Test with 20 transactions to verify basic functionality ./run-load-test.sh burst 20 arbitrum_to_base ``` ### Stress Test + ```bash # Send 100 transactions to test system under load ./run-load-test.sh burst 100 arbitrum_to_base ``` ### Long-term Stability Test + ```bash # Run for 2 hours with 3 transactions every 5 minutes ./run-load-test.sh sustained 3 5m 2h arbitrum_to_base ``` ### High Frequency Test + ```bash # Send 10 transactions every 30 seconds for 10 minutes ./run-load-test.sh sustained 10 30s 10m arbitrum_to_base ``` ### Very High Frequency Test + ```bash # Send 5 transactions every 10 seconds for 1 minute ./run-load-test.sh sustained 5 10s 1m arbitrum_to_base @@ -150,12 +167,14 @@ The sustained load test includes an `emergencyStop()` function that can be calle ## Troubleshooting ### Common Issues + 1. **Insufficient tokens**: Ensure Alice's account has enough USDT on both chains 2. **RPC rate limits**: Increase delays in the script if you encounter rate limiting 3. **Contract not deployed**: Verify all contract addresses in your `.env` file 4. **API key issues**: Ensure your Etherscan/Blockscout API keys are valid ### Debug Mode + Use `-vvv` flag in the shell script for maximum verbosity to debug issues. ## Safety Notes From 239f70935d0ff2fee6f3322bf15c99e0f6f2fa81 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Fri, 3 Oct 2025 12:34:40 +0200 Subject: [PATCH 06/14] fix: minor text change for clarity --- contracts/script/test/7683/BurstLoadTest.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol index 849628d4..15715a6c 100644 --- a/contracts/script/test/7683/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/BurstLoadTest.s.sol @@ -58,7 +58,7 @@ contract BurstLoadTest is Script { } // Print final results - console2.log("=== LOAD TEST COMPLETE ==="); + console2.log("=== BURST LOAD TEST COMPLETE ==="); console2.log("Total transactions:", maxTxs); console2.log("Successful:", successCount); console2.log("Failed:", failureCount); From 2dbdee046700fe5e672704e91fb35050c0921783 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Wed, 8 Oct 2025 06:58:05 -0400 Subject: [PATCH 07/14] fix: address comments on logs --- .../script/test/7683/BurstLoadTest.s.sol | 4 --- .../script/test/7683/SustainedLoadTest.s.sol | 27 ++----------------- contracts/script/test/7683/run-load-test.sh | 4 --- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol index 15715a6c..ae5d3e87 100644 --- a/contracts/script/test/7683/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/BurstLoadTest.s.sol @@ -168,10 +168,8 @@ contract BurstLoadTest is Script { ); emit TransactionSent(id, results[results.length - 1].txHash, true); successCount++; - console2.log("Transaction", transactionIndex + 1, "successful with nonce", nextNonce); } catch Error(string memory reason) { results.push(TestResult({ orderId: OrderEncoder.id(orderData), txHash: 0, timestamp: 0, success: false })); - // console2.log("Transaction", transactionIndex + 1, "failed with nonce", nextNonce, "reason:", reason); failureCount++; emit TransactionSent(OrderEncoder.id(orderData), 0, false); } @@ -231,11 +229,9 @@ contract BurstLoadTest is Script { ); emit TransactionSent(id, results[results.length - 1].txHash, true); successCount++; - console2.log("Transaction", transactionIndex + 1, "successful with nonce", nextNonce); } catch Error(string memory reason) { results.push(TestResult({ orderId: OrderEncoder.id(orderData), txHash: 0, timestamp: 0, success: false })); failureCount++; - // console2.log("Transaction", transactionIndex + 1, "failed with nonce", nextNonce, "reason:", reason); emit TransactionSent(OrderEncoder.id(orderData), 0, false); } diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol index 9ae2e0f8..6272e608 100644 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -64,11 +64,6 @@ contract SustainedLoadTest is Script { console2.log("Interval (seconds): %d", intervalSeconds); console2.log("Duration (seconds): %d", durationSeconds); console2.log("Test direction: %s", testDirection); - console2.log("ARB_T1_PULL_BASED_7683_PROXY_ADDR: %s", vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); - console2.log("BASE_T1_PULL_BASED_7683_PROXY_ADDR: %s", vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); - console2.log("ARBITRUM_SEPOLIA_USDT_ADDR: %s", vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - console2.log("BASE_SEPOLIA_USDT_ADDR: %s", vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - console2.log("ALICE_ADDRESS: %s", vm.addr(vm.envUint("ALICE_PRIVATE_KEY"))); // Validate configuration require(txsPerBatch > 0, "Transactions per batch must be > 0"); @@ -128,15 +123,6 @@ contract SustainedLoadTest is Script { inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); - // Debug nonce state before starting - console2.log("Checking used nonces for %s on Arbitrum", alice); - for (uint32 i = 0; i < 10; i++) { - console2.log("Nonce %d used: %s", i, l1_7683.usedNonces(alice, i)); - console2.log("Nonce %d valid: %s", i, l1_7683.isValidNonce(alice, i)); - } - - console2.log("Starting sustained Arbitrum -> Base load test with initial nonce: %d", nonceCounter); - uint256 batchNumber = 0; while (batchNumber < currentSession.totalBatches && currentSession.isActive) { batchNumber++; @@ -162,15 +148,6 @@ contract SustainedLoadTest is Script { inputToken.approve(address(l1_7683), type(uint256).max); vm.stopBroadcast(); - // Debug nonce state before starting - console2.log("Checking used nonces for %s on Base", alice); - for (uint32 i = 0; i < 10; i++) { - console2.log("Nonce %d used: %s", i, l1_7683.usedNonces(alice, i)); - console2.log("Nonce %d valid: %s", i, l1_7683.isValidNonce(alice, i)); - } - - console2.log("Starting sustained Base -> Arbitrum load test with initial nonce: %d", nonceCounter); - uint256 batchNumber = 0; while (batchNumber < currentSession.totalBatches && currentSession.isActive) { batchNumber++; @@ -199,7 +176,7 @@ contract SustainedLoadTest is Script { for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { // Find a valid (unused) nonce - uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop + uint256 maxAttempts = 100000; // Safety limit to prevent infinite loop uint256 attempts = 0; while ( l1_7683.usedNonces(alice, nonceCounter) @@ -308,7 +285,7 @@ contract SustainedLoadTest is Script { for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { // Find a valid (unused) nonce - uint256 maxAttempts = 1000; // Safety limit to prevent infinite loop + uint256 maxAttempts = 100000; // Safety limit to prevent infinite loop uint256 attempts = 0; while ( l1_7683.usedNonces(alice, nonceCounter) diff --git a/contracts/script/test/7683/run-load-test.sh b/contracts/script/test/7683/run-load-test.sh index 1bba04f9..f0358189 100755 --- a/contracts/script/test/7683/run-load-test.sh +++ b/contracts/script/test/7683/run-load-test.sh @@ -232,10 +232,6 @@ cd "$(dirname "$0")/../../.." forge script ./script/test/7683/${SCRIPT_NAME}.s.sol:${SCRIPT_NAME} \ --rpc-url $RPC_URL \ --broadcast \ - --verify \ - --verifier etherscan \ - --verifier-url $VERIFIER_URL \ - --etherscan-api-key $API_KEY \ -vvv echo "==========================================" From 5d1d7483d22340b8f3358dab5a9c80b328147125 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Thu, 23 Oct 2025 10:52:50 -0400 Subject: [PATCH 08/14] fix: pr comments, stop batching --- contracts/script/test/7683/BurstLoadTest.s.sol | 5 +++-- contracts/script/test/7683/SustainedLoadTest.s.sol | 5 +++-- contracts/script/test/7683/run-load-test.sh | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol index ae5d3e87..efd0c66f 100644 --- a/contracts/script/test/7683/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/BurstLoadTest.s.sol @@ -9,6 +9,7 @@ import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncode import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; +import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; /** * @title BurstLoadTest @@ -68,7 +69,7 @@ contract BurstLoadTest is Script { } function _runArbitrumToBaseLoadTest(uint256 maxTxs) internal { - vm.createSelectFork(vm.rpcUrl("arbitrum_sepolia")); + selectMainnetOrSepoliaFork("arbitrum"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); @@ -92,7 +93,7 @@ contract BurstLoadTest is Script { } function _runBaseToArbitrumLoadTest(uint256 maxTxs) internal { - vm.createSelectFork(vm.rpcUrl("base_sepolia")); + selectMainnetOrSepoliaFork("base"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol index 6272e608..31b33e5b 100644 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -9,6 +9,7 @@ import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncode import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; +import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; /** * @title SustainedLoadTest @@ -112,7 +113,7 @@ contract SustainedLoadTest is Script { } function _runSustainedArbitrumToBaseTest() internal { - vm.createSelectFork(vm.rpcUrl("arbitrum_sepolia")); + selectMainnetOrSepoliaFork("arbitrum"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); @@ -137,7 +138,7 @@ contract SustainedLoadTest is Script { } function _runSustainedBaseToArbitrumTest() internal { - vm.createSelectFork(vm.rpcUrl("base_sepolia")); + selectMainnetOrSepoliaFork("base"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); diff --git a/contracts/script/test/7683/run-load-test.sh b/contracts/script/test/7683/run-load-test.sh index f0358189..55b3112c 100755 --- a/contracts/script/test/7683/run-load-test.sh +++ b/contracts/script/test/7683/run-load-test.sh @@ -232,7 +232,9 @@ cd "$(dirname "$0")/../../.." forge script ./script/test/7683/${SCRIPT_NAME}.s.sol:${SCRIPT_NAME} \ --rpc-url $RPC_URL \ --broadcast \ - -vvv + --ffi \ + --slow \ + --verbose echo "==========================================" echo "Load test completed at: $(date)" From d524ef29b5073feaa63810bb009ecf2090d5d38e Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Thu, 23 Oct 2025 11:10:03 -0400 Subject: [PATCH 09/14] fix: limits --- contracts/script/test/7683/SustainedLoadTest.s.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol index 31b33e5b..57f1b80b 100644 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -177,7 +177,7 @@ contract SustainedLoadTest is Script { for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { // Find a valid (unused) nonce - uint256 maxAttempts = 100000; // Safety limit to prevent infinite loop + uint256 maxAttempts = uint256.max; // Safety limit to prevent infinite loop uint256 attempts = 0; while ( l1_7683.usedNonces(alice, nonceCounter) @@ -286,7 +286,7 @@ contract SustainedLoadTest is Script { for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { // Find a valid (unused) nonce - uint256 maxAttempts = 100000; // Safety limit to prevent infinite loop + uint256 maxAttempts = uint256.max;; // Safety limit to prevent infinite loop uint256 attempts = 0; while ( l1_7683.usedNonces(alice, nonceCounter) From 127ece77d224b906c0c3d30a59c40425ec606741 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Thu, 23 Oct 2025 11:22:59 -0400 Subject: [PATCH 10/14] fix: load test subfoldering --- .../test/7683/{ => loadtesting}/BurstLoadTest.s.sol | 10 +++++----- contracts/script/test/7683/{ => loadtesting}/README.md | 0 .../7683/{ => loadtesting}/SustainedLoadTest.s.sol | 10 +++++----- .../test/7683/{ => loadtesting}/run-load-test.sh | 0 4 files changed, 10 insertions(+), 10 deletions(-) rename contracts/script/test/7683/{ => loadtesting}/BurstLoadTest.s.sol (96%) rename contracts/script/test/7683/{ => loadtesting}/README.md (100%) rename contracts/script/test/7683/{ => loadtesting}/SustainedLoadTest.s.sol (97%) rename contracts/script/test/7683/{ => loadtesting}/run-load-test.sh (100%) diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol similarity index 96% rename from contracts/script/test/7683/BurstLoadTest.s.sol rename to contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol index efd0c66f..f5d1a217 100644 --- a/contracts/script/test/7683/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol @@ -5,11 +5,11 @@ import { Script } from "forge-std/Script.sol"; import { console2 } from "forge-std/console2.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; -import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; -import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; -import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; -import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; -import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; +import { OrderData, OrderEncoder } from "../../../../src/libraries/7683/OrderEncoder.sol"; +import { OnchainCrossChainOrder } from "../../../../src/interfaces/IERC7683.sol"; +import { T1ERC7683 } from "../../../../src/7683/T1ERC7683.sol"; +import { T1Constants } from "../../../../src/libraries/constants/T1Constants.sol"; +import { DeploymentUtils } from "../../../lib/DeploymentUtils.sol"; /** * @title BurstLoadTest diff --git a/contracts/script/test/7683/README.md b/contracts/script/test/7683/loadtesting/README.md similarity index 100% rename from contracts/script/test/7683/README.md rename to contracts/script/test/7683/loadtesting/README.md diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol similarity index 97% rename from contracts/script/test/7683/SustainedLoadTest.s.sol rename to contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol index 57f1b80b..49953b78 100644 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol @@ -5,11 +5,11 @@ import { Script } from "forge-std/Script.sol"; import { console2 } from "forge-std/console2.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; -import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; -import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; -import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; -import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; -import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; +import { OrderData, OrderEncoder } from "../../../../src/libraries/7683/OrderEncoder.sol"; +import { OnchainCrossChainOrder } from "../../../../src/interfaces/IERC7683.sol"; +import { T1ERC7683 } from "../../../../src/7683/T1ERC7683.sol"; +import { T1Constants } from "../../../../src/libraries/constants/T1Constants.sol"; +import { DeploymentUtils } from "../../../lib/DeploymentUtils.sol"; /** * @title SustainedLoadTest diff --git a/contracts/script/test/7683/run-load-test.sh b/contracts/script/test/7683/loadtesting/run-load-test.sh similarity index 100% rename from contracts/script/test/7683/run-load-test.sh rename to contracts/script/test/7683/loadtesting/run-load-test.sh From 36dd42edbdf5473abd10c976701f5c7cf82b2c3d Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Thu, 23 Oct 2025 11:24:00 -0400 Subject: [PATCH 11/14] fix: extra ; --- contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol b/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol index 49953b78..0de41aa9 100644 --- a/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol @@ -286,7 +286,7 @@ contract SustainedLoadTest is Script { for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { // Find a valid (unused) nonce - uint256 maxAttempts = uint256.max;; // Safety limit to prevent infinite loop + uint256 maxAttempts = uint256.max; // Safety limit to prevent infinite loop uint256 attempts = 0; while ( l1_7683.usedNonces(alice, nonceCounter) From 8c3b7f554a66e4a0c6da5b46b987380dde889831 Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Thu, 23 Oct 2025 11:28:14 -0400 Subject: [PATCH 12/14] fix: syntax --- .../script/test/7683/loadtesting/BurstLoadTest.s.sol | 6 +++--- .../test/7683/loadtesting/SustainedLoadTest.s.sol | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol b/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol index f5d1a217..ae3a9e00 100644 --- a/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol @@ -16,7 +16,7 @@ import { DeploymentUtils } from "../../../lib/DeploymentUtils.sol"; * @dev Load testing script for 7683 contracts that sends a configurable number of transactions * sequentially to avoid RPC rate limiting. Supports both Arbitrum->Base and Base->Arbitrum flows. */ -contract BurstLoadTest is Script { +contract BurstLoadTest is Script, DeploymentUtils { // Configuration from environment variables uint256 public constant MAX_TRANSACTIONS = 10; // Default, can be overridden by env var uint256 public constant AMOUNT_IN = 10; // 0.00001 USDT (assuming 6 decimals) @@ -69,7 +69,7 @@ contract BurstLoadTest is Script { } function _runArbitrumToBaseLoadTest(uint256 maxTxs) internal { - selectMainnetOrSepoliaFork("arbitrum"); + DeploymentUtils.selectMainnetOrSepoliaFork("arbitrum"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); @@ -93,7 +93,7 @@ contract BurstLoadTest is Script { } function _runBaseToArbitrumLoadTest(uint256 maxTxs) internal { - selectMainnetOrSepoliaFork("base"); + DeploymentUtils.selectMainnetOrSepoliaFork("base"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); diff --git a/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol b/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol index 0de41aa9..e58a3395 100644 --- a/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol @@ -16,7 +16,7 @@ import { DeploymentUtils } from "../../../lib/DeploymentUtils.sol"; * @dev Sustained load testing script for 7683 contracts that sends n transactions * every t seconds for a configurable duration. Supports both Arbitrum->Base and Base->Arbitrum flows. */ -contract SustainedLoadTest is Script { +contract SustainedLoadTest is Script, DeploymentUtils { // Configuration from environment variables uint256 public constant DEFAULT_TRANSACTIONS_PER_BATCH = 5; uint256 public constant DEFAULT_INTERVAL_SECONDS = 60; // 1 minute @@ -113,7 +113,7 @@ contract SustainedLoadTest is Script { } function _runSustainedArbitrumToBaseTest() internal { - selectMainnetOrSepoliaFork("arbitrum"); + DeploymentUtils.selectMainnetOrSepoliaFork("arbitrum"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); @@ -138,7 +138,7 @@ contract SustainedLoadTest is Script { } function _runSustainedBaseToArbitrumTest() internal { - selectMainnetOrSepoliaFork("base"); + DeploymentUtils.selectMainnetOrSepoliaFork("base"); T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); address alice = vm.addr(alicePk); @@ -177,7 +177,7 @@ contract SustainedLoadTest is Script { for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { // Find a valid (unused) nonce - uint256 maxAttempts = uint256.max; // Safety limit to prevent infinite loop + uint256 maxAttempts = type(uint256).max; // Safety limit to prevent infinite loop uint256 attempts = 0; while ( l1_7683.usedNonces(alice, nonceCounter) @@ -286,7 +286,7 @@ contract SustainedLoadTest is Script { for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { // Find a valid (unused) nonce - uint256 maxAttempts = uint256.max; // Safety limit to prevent infinite loop + uint256 maxAttempts = type(uint256).max; // Safety limit to prevent infinite loop uint256 attempts = 0; while ( l1_7683.usedNonces(alice, nonceCounter) From 7da2862208682244ca9149eac09c7d19aa14f6cd Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Fri, 24 Oct 2025 13:18:46 -0400 Subject: [PATCH 13/14] Revert "fix: load test subfoldering" This reverts commit 127ece77d224b906c0c3d30a59c40425ec606741. --- .../test/7683/{loadtesting => }/BurstLoadTest.s.sol | 10 +++++----- contracts/script/test/7683/{loadtesting => }/README.md | 0 .../7683/{loadtesting => }/SustainedLoadTest.s.sol | 10 +++++----- .../test/7683/{loadtesting => }/run-load-test.sh | 0 4 files changed, 10 insertions(+), 10 deletions(-) rename contracts/script/test/7683/{loadtesting => }/BurstLoadTest.s.sol (96%) rename contracts/script/test/7683/{loadtesting => }/README.md (100%) rename contracts/script/test/7683/{loadtesting => }/SustainedLoadTest.s.sol (97%) rename contracts/script/test/7683/{loadtesting => }/run-load-test.sh (100%) diff --git a/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol similarity index 96% rename from contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol rename to contracts/script/test/7683/BurstLoadTest.s.sol index ae3a9e00..130900e3 100644 --- a/contracts/script/test/7683/loadtesting/BurstLoadTest.s.sol +++ b/contracts/script/test/7683/BurstLoadTest.s.sol @@ -5,11 +5,11 @@ import { Script } from "forge-std/Script.sol"; import { console2 } from "forge-std/console2.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; -import { OrderData, OrderEncoder } from "../../../../src/libraries/7683/OrderEncoder.sol"; -import { OnchainCrossChainOrder } from "../../../../src/interfaces/IERC7683.sol"; -import { T1ERC7683 } from "../../../../src/7683/T1ERC7683.sol"; -import { T1Constants } from "../../../../src/libraries/constants/T1Constants.sol"; -import { DeploymentUtils } from "../../../lib/DeploymentUtils.sol"; +import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; +import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; +import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; +import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; +import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; /** * @title BurstLoadTest diff --git a/contracts/script/test/7683/loadtesting/README.md b/contracts/script/test/7683/README.md similarity index 100% rename from contracts/script/test/7683/loadtesting/README.md rename to contracts/script/test/7683/README.md diff --git a/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol similarity index 97% rename from contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol rename to contracts/script/test/7683/SustainedLoadTest.s.sol index e58a3395..86425cb3 100644 --- a/contracts/script/test/7683/loadtesting/SustainedLoadTest.s.sol +++ b/contracts/script/test/7683/SustainedLoadTest.s.sol @@ -5,11 +5,11 @@ import { Script } from "forge-std/Script.sol"; import { console2 } from "forge-std/console2.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; -import { OrderData, OrderEncoder } from "../../../../src/libraries/7683/OrderEncoder.sol"; -import { OnchainCrossChainOrder } from "../../../../src/interfaces/IERC7683.sol"; -import { T1ERC7683 } from "../../../../src/7683/T1ERC7683.sol"; -import { T1Constants } from "../../../../src/libraries/constants/T1Constants.sol"; -import { DeploymentUtils } from "../../../lib/DeploymentUtils.sol"; +import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; +import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; +import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; +import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; +import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; /** * @title SustainedLoadTest diff --git a/contracts/script/test/7683/loadtesting/run-load-test.sh b/contracts/script/test/7683/run-load-test.sh similarity index 100% rename from contracts/script/test/7683/loadtesting/run-load-test.sh rename to contracts/script/test/7683/run-load-test.sh From bbac22aa8f3e2991d1d7740d634ae1bef9e59ccb Mon Sep 17 00:00:00 2001 From: Ryan Collins Date: Fri, 24 Oct 2025 13:35:23 -0400 Subject: [PATCH 14/14] feat: bash load test --- .../script/test/7683/BurstLoadTest.s.sol | 264 ----------- contracts/script/test/7683/README.md | 185 -------- .../script/test/7683/SustainedLoadTest.s.sol | 428 ------------------ .../script/test/7683/loadtesting/loadtest.sh | 312 +++++++++++++ contracts/script/test/7683/run-load-test.sh | 241 ---------- 5 files changed, 312 insertions(+), 1118 deletions(-) delete mode 100644 contracts/script/test/7683/BurstLoadTest.s.sol delete mode 100644 contracts/script/test/7683/README.md delete mode 100644 contracts/script/test/7683/SustainedLoadTest.s.sol create mode 100755 contracts/script/test/7683/loadtesting/loadtest.sh delete mode 100755 contracts/script/test/7683/run-load-test.sh diff --git a/contracts/script/test/7683/BurstLoadTest.s.sol b/contracts/script/test/7683/BurstLoadTest.s.sol deleted file mode 100644 index 130900e3..00000000 --- a/contracts/script/test/7683/BurstLoadTest.s.sol +++ /dev/null @@ -1,264 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { Script } from "forge-std/Script.sol"; -import { console2 } from "forge-std/console2.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; -import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; -import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; -import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; -import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; -import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; - -/** - * @title BurstLoadTest - * @dev Load testing script for 7683 contracts that sends a configurable number of transactions - * sequentially to avoid RPC rate limiting. Supports both Arbitrum->Base and Base->Arbitrum flows. - */ -contract BurstLoadTest is Script, DeploymentUtils { - // Configuration from environment variables - uint256 public constant MAX_TRANSACTIONS = 10; // Default, can be overridden by env var - uint256 public constant AMOUNT_IN = 10; // 0.00001 USDT (assuming 6 decimals) - - // Test results tracking - struct TestResult { - bytes32 orderId; - uint256 txHash; - uint256 timestamp; - bool success; - } - - TestResult[] public results; - uint256 public successCount; - uint256 public failureCount; - - event TransactionSent(bytes32 indexed orderId, uint256 txHash, bool success); - event LoadTestComplete(uint256 totalTransactions, uint256 successCount, uint256 failureCount); - - function run() external { - // Get configuration from environment - uint256 maxTxs = vm.envOr("LOAD_TEST_MAX_TRANSACTIONS", uint256(MAX_TRANSACTIONS)); - string memory testDirection = vm.envOr("LOAD_TEST_DIRECTION", string("arbitrum_to_base")); - - console2.log("Starting load test with", maxTxs, "transactions"); - console2.log("Test direction:", testDirection); - - // Reset counters - delete results; - successCount = 0; - failureCount = 0; - - // Execute load test based on direction - if (keccak256(bytes(testDirection)) == keccak256(bytes("arbitrum_to_base"))) { - _runArbitrumToBaseLoadTest(maxTxs); - } else if (keccak256(bytes(testDirection)) == keccak256(bytes("base_to_arbitrum"))) { - _runBaseToArbitrumLoadTest(maxTxs); - } else { - revert("Invalid test direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'"); - } - - // Print final results - console2.log("=== BURST LOAD TEST COMPLETE ==="); - console2.log("Total transactions:", maxTxs); - console2.log("Successful:", successCount); - console2.log("Failed:", failureCount); - console2.log("Success rate:", (successCount * 100) / maxTxs, "%"); - - emit LoadTestComplete(maxTxs, successCount, failureCount); - } - - function _runArbitrumToBaseLoadTest(uint256 maxTxs) internal { - DeploymentUtils.selectMainnetOrSepoliaFork("arbitrum"); - - T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); - uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); - address alice = vm.addr(alicePk); - - // Approve tokens once at the beginning - vm.startBroadcast(alicePk); - ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - inputToken.approve(address(l1_7683), type(uint256).max); - vm.stopBroadcast(); - - console2.log("Starting Arbitrum -> Base load test..."); - - // Send transactions sequentially - for (uint256 i = 0; i < maxTxs; i++) { - _sendArbitrumToBaseTransaction(l1_7683, alicePk, alice, i); - - // Small delay to avoid overwhelming the RPC - vm.sleep(100); // 100ms delay between transactions - } - } - - function _runBaseToArbitrumLoadTest(uint256 maxTxs) internal { - DeploymentUtils.selectMainnetOrSepoliaFork("base"); - - T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); - uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); - address alice = vm.addr(alicePk); - - // Approve tokens once at the beginning - vm.startBroadcast(alicePk); - ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - inputToken.approve(address(l1_7683), type(uint256).max); - vm.stopBroadcast(); - - console2.log("Starting Base -> Arbitrum load test..."); - - // Send transactions sequentially - for (uint256 i = 0; i < maxTxs; i++) { - _sendBaseToArbitrumTransaction(l1_7683, alicePk, alice, i); - - // Small delay to avoid overwhelming the RPC - vm.sleep(100); // 100ms delay between transactions - } - } - - function _sendArbitrumToBaseTransaction( - T1ERC7683 l1_7683, - uint256 alicePk, - address alice, - uint256 transactionIndex // Renamed for clarity - ) - internal - { - vm.startBroadcast(alicePk); - - ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - ERC20 outputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - - // Find the next valid nonce - uint32 nextNonce = 0; - while (l1_7683.usedNonces(alice, nextNonce) || !l1_7683.isValidNonce(alice, nextNonce)) { - nextNonce++; - require(nextNonce < type(uint32).max, "No valid nonce available"); - } - - OrderData memory orderData = OrderData({ - sender: TypeCasts.addressToBytes32(alice), - recipient: TypeCasts.addressToBytes32(alice), - inputToken: TypeCasts.addressToBytes32(address(inputToken)), - outputToken: TypeCasts.addressToBytes32(address(outputToken)), - amountIn: AMOUNT_IN, - minAmountOut: AMOUNT_IN * 9 / 10, - senderNonce: nextNonce, - originDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), - destinationDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), - destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), - closedAuction: true, - data: new bytes(0) - }); - - bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - // Call open with try-catch to handle reverts - try l1_7683.open(order) { - bytes32 id = OrderEncoder.id(orderData); - results.push( - TestResult({ - orderId: id, - txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), - timestamp: 0, - success: true - }) - ); - emit TransactionSent(id, results[results.length - 1].txHash, true); - successCount++; - } catch Error(string memory reason) { - results.push(TestResult({ orderId: OrderEncoder.id(orderData), txHash: 0, timestamp: 0, success: false })); - failureCount++; - emit TransactionSent(OrderEncoder.id(orderData), 0, false); - } - - vm.stopBroadcast(); - } - - function _sendBaseToArbitrumTransaction( - T1ERC7683 l1_7683, - uint256 alicePk, - address alice, - uint256 transactionIndex - ) - internal - { - vm.startBroadcast(alicePk); - - ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - ERC20 outputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - - // Find the next valid nonce - uint32 nextNonce = 0; - while (l1_7683.usedNonces(alice, nextNonce) || !l1_7683.isValidNonce(alice, nextNonce)) { - nextNonce++; - require(nextNonce < type(uint32).max, "No valid nonce available"); - } - - OrderData memory orderData = OrderData({ - sender: TypeCasts.addressToBytes32(alice), - recipient: TypeCasts.addressToBytes32(alice), - inputToken: TypeCasts.addressToBytes32(address(inputToken)), - outputToken: TypeCasts.addressToBytes32(address(outputToken)), - amountIn: AMOUNT_IN, - minAmountOut: AMOUNT_IN * 9 / 10, - senderNonce: nextNonce, - originDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), - destinationDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), - destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), - closedAuction: true, - data: new bytes(0) - }); - - bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - try l1_7683.open(order) { - bytes32 id = OrderEncoder.id(orderData); - results.push( - TestResult({ - orderId: id, - txHash: uint256(keccak256(abi.encodePacked(nextNonce, "burst_test"))), - timestamp: 0, - success: true - }) - ); - emit TransactionSent(id, results[results.length - 1].txHash, true); - successCount++; - } catch Error(string memory reason) { - results.push(TestResult({ orderId: OrderEncoder.id(orderData), txHash: 0, timestamp: 0, success: false })); - failureCount++; - emit TransactionSent(OrderEncoder.id(orderData), 0, false); - } - - vm.stopBroadcast(); - } - - function _prepareOnchainOrder( - bytes memory orderData, - uint32 fillDeadline, - bytes32 orderDataType - ) - internal - pure - returns (OnchainCrossChainOrder memory) - { - return - OnchainCrossChainOrder({ fillDeadline: fillDeadline, orderDataType: orderDataType, orderData: orderData }); - } - - // Utility functions for analysis - function getResults() external view returns (TestResult[] memory) { - return results; - } - - function getSuccessRate() external view returns (uint256) { - if (successCount + failureCount == 0) return 0; - return (successCount * 100) / (successCount + failureCount); - } -} diff --git a/contracts/script/test/7683/README.md b/contracts/script/test/7683/README.md deleted file mode 100644 index 1313cb8a..00000000 --- a/contracts/script/test/7683/README.md +++ /dev/null @@ -1,185 +0,0 @@ -# 7683 Load Testing Scripts - -This directory contains load testing scripts for the 7683 cross-chain contracts. - -## Scripts - -### BurstLoadTest.s.sol - -Sends a configurable number of transactions sequentially as fast as possible for burst load testing. - -### SustainedLoadTest.s.sol - -Sends n transactions every t minutes for a sustained load test over a configurable duration. - -### run-load-test.sh - -Shell script runner that can execute either burst or sustained load tests with proper configuration. - -## Usage - -### Prerequisites - -- Ensure your `.env` file is properly configured with all required variables -- Make sure you have sufficient tokens for testing (USDT on both chains) -- Set the required API keys: `ARBISCAN_API_KEY` and `BASESCAN_API_KEY` - -### Burst Load Testing - -Send a specified number of transactions as fast as possible: - -```bash -# Send 50 transactions from Arbitrum to Base -./run-load-test.sh burst 50 arbitrum_to_base - -# Send 100 transactions from Base to Arbitrum -./run-load-test.sh burst 100 base_to_arbitrum - -# Send 25 transactions (defaults to arbitrum_to_base) -./run-load-test.sh burst 25 -``` - -### Sustained Load Testing - -Send n transactions every t interval for a specified duration with flexible time units: - -```bash -# Send 5 transactions every 2 minutes for 20 minutes (Arbitrum to Base) -./run-load-test.sh sustained 5 2m 20m arbitrum_to_base - -# Send 10 transactions every 30 seconds for 5 minutes (Base to Arbitrum) -./run-load-test.sh sustained 10 30s 5m base_to_arbitrum - -# Send 3 transactions every 1 hour for 2 hours -./run-load-test.sh sustained 3 1h 2h arbitrum_to_base - -# Use defaults: 5 transactions every 1 minute for 10 minutes -./run-load-test.sh sustained -``` - -**Time Units:** - -- `s` = seconds (e.g., `30s`) -- `m` = minutes (e.g., `2m`) -- `h` = hours (e.g., `1h`) - -## Environment Variables - -### Required for Both Modes - -- `ALICE_PRIVATE_KEY` - Private key for the test account -- `ARBISCAN_API_KEY` - API key for Arbitrum verification -- `BASESCAN_API_KEY` - API key for Base verification - -### Required Contract Addresses - -- `ARB_T1_PULL_BASED_7683_PROXY_ADDR` - Arbitrum 7683 contract -- `BASE_T1_PULL_BASED_7683_PROXY_ADDR` - Base 7683 contract -- `ARBITRUM_SEPOLIA_USDT_ADDR` - USDT on Arbitrum Sepolia -- `BASE_SEPOLIA_USDT_ADDR` - USDT on Base Sepolia - -### Optional Configuration - -- `LOAD_TEST_MAX_TRANSACTIONS` - Override default max transactions for burst mode -- `SUSTAINED_TXS_PER_BATCH` - Override default transactions per batch for sustained mode -- `SUSTAINED_INTERVAL_MINUTES` - Override default interval for sustained mode -- `SUSTAINED_DURATION_MINUTES` - Override default duration for sustained mode -- `LOAD_TEST_DIRECTION` - Override test direction (arbitrum_to_base or base_to_arbitrum) - -## Test Flow - -Both scripts follow the same basic flow: - -1. **Setup**: Connect to the appropriate RPC and load contract addresses -2. **Token Approval**: Approve the maximum amount of USDT for the 7683 contract -3. **Transaction Execution**: - - Create cross-chain order data with unique nonces - - Call `T1ERC7683.open()` with the order - - Track success/failure rates -4. **Reporting**: Log results and emit events for analysis - -## Monitoring - -### Events Emitted - -- `TransactionSent(bytes32 orderId, uint256 batchNumber, bool success)` -- `LoadTestComplete(uint256 totalTransactions, uint256 successCount, uint256 failureCount)` (burst mode) -- `BatchCompleted(uint256 batchNumber, uint256 successCount, uint256 failureCount)` (sustained mode) -- `SessionStarted/Ended` events (sustained mode) - -### Console Output - -- Real-time progress updates -- Success/failure counts -- Final statistics including success rates -- Error messages for failed transactions - -## Rate Limiting Considerations - -- **Burst Mode**: Includes 100ms delays between transactions to avoid overwhelming RPC endpoints -- **Sustained Mode**: Configurable intervals between batches to maintain sustained load -- Both modes send transactions sequentially to avoid RPC rate limits - -## Emergency Controls - -### Sustained Mode Emergency Stop - -The sustained load test includes an `emergencyStop()` function that can be called to immediately halt an active test -session. - -## Example Test Scenarios - -### Quick Burst Test - -```bash -# Test with 20 transactions to verify basic functionality -./run-load-test.sh burst 20 arbitrum_to_base -``` - -### Stress Test - -```bash -# Send 100 transactions to test system under load -./run-load-test.sh burst 100 arbitrum_to_base -``` - -### Long-term Stability Test - -```bash -# Run for 2 hours with 3 transactions every 5 minutes -./run-load-test.sh sustained 3 5m 2h arbitrum_to_base -``` - -### High Frequency Test - -```bash -# Send 10 transactions every 30 seconds for 10 minutes -./run-load-test.sh sustained 10 30s 10m arbitrum_to_base -``` - -### Very High Frequency Test - -```bash -# Send 5 transactions every 10 seconds for 1 minute -./run-load-test.sh sustained 5 10s 1m arbitrum_to_base -``` - -## Troubleshooting - -### Common Issues - -1. **Insufficient tokens**: Ensure Alice's account has enough USDT on both chains -2. **RPC rate limits**: Increase delays in the script if you encounter rate limiting -3. **Contract not deployed**: Verify all contract addresses in your `.env` file -4. **API key issues**: Ensure your Etherscan/Blockscout API keys are valid - -### Debug Mode - -Use `-vvv` flag in the shell script for maximum verbosity to debug issues. - -## Safety Notes - -- These scripts send real transactions on testnets -- Monitor your test account balances -- Use appropriate delays to avoid overwhelming RPC endpoints -- Start with small transaction counts to verify functionality diff --git a/contracts/script/test/7683/SustainedLoadTest.s.sol b/contracts/script/test/7683/SustainedLoadTest.s.sol deleted file mode 100644 index 86425cb3..00000000 --- a/contracts/script/test/7683/SustainedLoadTest.s.sol +++ /dev/null @@ -1,428 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import { Script } from "forge-std/Script.sol"; -import { console2 } from "forge-std/console2.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol"; -import { OrderData, OrderEncoder } from "../../../src/libraries/7683/OrderEncoder.sol"; -import { OnchainCrossChainOrder } from "../../../src/interfaces/IERC7683.sol"; -import { T1ERC7683 } from "../../../src/7683/T1ERC7683.sol"; -import { T1Constants } from "../../../src/libraries/constants/T1Constants.sol"; -import { DeploymentUtils } from "../../lib/DeploymentUtils.sol"; - -/** - * @title SustainedLoadTest - * @dev Sustained load testing script for 7683 contracts that sends n transactions - * every t seconds for a configurable duration. Supports both Arbitrum->Base and Base->Arbitrum flows. - */ -contract SustainedLoadTest is Script, DeploymentUtils { - // Configuration from environment variables - uint256 public constant DEFAULT_TRANSACTIONS_PER_BATCH = 5; - uint256 public constant DEFAULT_INTERVAL_SECONDS = 60; // 1 minute - uint256 public constant DEFAULT_DURATION_SECONDS = 600; // 10 minutes - uint256 public constant AMOUNT_IN = 10; // 0.00001 USDT (assuming 6 decimals) - - // Test state - struct TestSession { - uint256 startTime; - uint256 endTime; - uint256 totalBatches; - uint256 transactionsPerBatch; - uint256 intervalMinutes; - bool isActive; - } - - struct BatchResult { - uint256 batchNumber; - uint256 timestamp; - uint256 successCount; - uint256 failureCount; - bytes32[] orderIds; - } - - TestSession public currentSession; - BatchResult[] public batchResults; - uint256 public totalTransactions; - uint256 public totalSuccesses; - uint256 public totalFailures; - uint256 public nonceCounter; - - event SessionStarted(uint256 startTime, uint256 endTime, uint256 transactionsPerBatch, uint256 intervalSeconds); - event BatchCompleted(uint256 batchNumber, uint256 successCount, uint256 failureCount); - event SessionEnded(uint256 totalTransactions, uint256 totalSuccesses, uint256 totalFailures); - event TransactionSent(bytes32 indexed orderId, uint256 batchNumber, bool success); - - function run() external { - // Get configuration from environment - uint256 txsPerBatch = vm.envOr("SUSTAINED_TXS_PER_BATCH", uint256(DEFAULT_TRANSACTIONS_PER_BATCH)); - uint256 intervalSeconds = vm.envOr("SUSTAINED_INTERVAL_SECONDS", uint256(DEFAULT_INTERVAL_SECONDS)); - uint256 durationSeconds = vm.envOr("SUSTAINED_DURATION_SECONDS", uint256(DEFAULT_DURATION_SECONDS)); - string memory testDirection = vm.envOr("LOAD_TEST_DIRECTION", string("arbitrum_to_base")); - - console2.log("=== STARTING SUSTAINED LOAD TEST ==="); - console2.log("Transactions per batch: %d", txsPerBatch); - console2.log("Interval (seconds): %d", intervalSeconds); - console2.log("Duration (seconds): %d", durationSeconds); - console2.log("Test direction: %s", testDirection); - - // Validate configuration - require(txsPerBatch > 0, "Transactions per batch must be > 0"); - require(intervalSeconds > 0, "Interval must be > 0"); - require(durationSeconds > 0, "Duration must be > 0"); - - // Initialize session - currentSession = TestSession({ - startTime: 0, // Not used for timing in script - endTime: durationSeconds, // Duration in seconds - totalBatches: durationSeconds / intervalSeconds, - transactionsPerBatch: txsPerBatch, - intervalMinutes: intervalSeconds / 60, // Convert to minutes for compatibility - isActive: true - }); - - // Reset counters - delete batchResults; - totalTransactions = 0; - totalSuccesses = 0; - totalFailures = 0; - nonceCounter = 0; - - emit SessionStarted(currentSession.startTime, currentSession.endTime, txsPerBatch, intervalSeconds); - - // Execute sustained load test based on direction - if (keccak256(bytes(testDirection)) == keccak256(bytes("arbitrum_to_base"))) { - _runSustainedArbitrumToBaseTest(); - } else if (keccak256(bytes(testDirection)) == keccak256(bytes("base_to_arbitrum"))) { - _runSustainedBaseToArbitrumTest(); - } else { - revert("Invalid test direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'"); - } - - // Mark session as ended - currentSession.isActive = false; - - // Print final results - console2.log("=== SUSTAINED LOAD TEST COMPLETE ==="); - console2.log("Total transactions: %d", totalTransactions); - console2.log("Total successes: %d", totalSuccesses); - console2.log("Total failures: %d", totalFailures); - console2.log("Success rate: %d%%", totalTransactions > 0 ? (totalSuccesses * 100) / totalTransactions : 0); - console2.log("Total batches: %d", batchResults.length); - emit SessionEnded(totalTransactions, totalSuccesses, totalFailures); - } - - function _runSustainedArbitrumToBaseTest() internal { - DeploymentUtils.selectMainnetOrSepoliaFork("arbitrum"); - T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")); - uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); - address alice = vm.addr(alicePk); - - // Approve tokens once at the beginning - vm.startBroadcast(alicePk); - ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - inputToken.approve(address(l1_7683), type(uint256).max); - vm.stopBroadcast(); - - uint256 batchNumber = 0; - while (batchNumber < currentSession.totalBatches && currentSession.isActive) { - batchNumber++; - _executeBatchArbitrumToBase(l1_7683, alicePk, alice, batchNumber); - // Wait for next interval (unless it's the last batch) - if (batchNumber < currentSession.totalBatches) { - uint256 waitTime = currentSession.intervalMinutes * 60; - console2.log("Waiting %d seconds for next batch...", waitTime); - vm.sleep(waitTime * 1000); // Convert to milliseconds - } - } - } - - function _runSustainedBaseToArbitrumTest() internal { - DeploymentUtils.selectMainnetOrSepoliaFork("base"); - T1ERC7683 l1_7683 = T1ERC7683(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")); - uint256 alicePk = vm.envUint("ALICE_PRIVATE_KEY"); - address alice = vm.addr(alicePk); - - // Approve tokens once at the beginning - vm.startBroadcast(alicePk); - ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - inputToken.approve(address(l1_7683), type(uint256).max); - vm.stopBroadcast(); - - uint256 batchNumber = 0; - while (batchNumber < currentSession.totalBatches && currentSession.isActive) { - batchNumber++; - _executeBatchBaseToArbitrum(l1_7683, alicePk, alice, batchNumber); - // Wait for next interval (unless it's the last batch) - if (batchNumber < currentSession.totalBatches) { - uint256 waitTime = currentSession.intervalMinutes * 60; - console2.log("Waiting %d seconds for next batch...", waitTime); - vm.sleep(waitTime * 1000); // Convert to milliseconds - } - } - } - - function _executeBatchArbitrumToBase( - T1ERC7683 l1_7683, - uint256 alicePk, - address alice, - uint256 batchNumber - ) - internal - { - console2.log("=== Executing Batch %d ===", batchNumber); - bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); - uint256 batchSuccesses = 0; - uint256 batchFailures = 0; - - for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { - // Find a valid (unused) nonce - uint256 maxAttempts = type(uint256).max; // Safety limit to prevent infinite loop - uint256 attempts = 0; - while ( - l1_7683.usedNonces(alice, nonceCounter) - || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts - ) { - nonceCounter++; - attempts++; - } - if (attempts >= maxAttempts) { - console2.log("Transaction %d failed: Could not find valid nonce", i); - batchFailures++; - totalFailures++; - continue; - } - - vm.startBroadcast(alicePk); - ERC20 inputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - ERC20 outputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - - // Log allowance and balance - uint256 allowance = inputToken.allowance(alice, address(l1_7683)); - uint256 balance = inputToken.balanceOf(alice); - console2.log("Transaction %d: Allowance: %d, Balance: %d", i, allowance, balance); - - // Log Ethereum nonce - uint256 ethNonce = vm.getNonce(alice); - console2.log("Transaction %d: Ethereum nonce: %d, SenderNonce: %d", i, ethNonce, nonceCounter); - - // Prepare order data - OrderData memory orderData = OrderData({ - sender: TypeCasts.addressToBytes32(alice), - recipient: TypeCasts.addressToBytes32(alice), - inputToken: TypeCasts.addressToBytes32(address(inputToken)), - outputToken: TypeCasts.addressToBytes32(address(outputToken)), - amountIn: AMOUNT_IN, - minAmountOut: AMOUNT_IN * 9 / 10, - senderNonce: uint32(nonceCounter), - originDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), - destinationDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), - destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("BASE_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), // 30 minutes - closedAuction: true, - data: new bytes(0) - }); - - bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - // Try submitting the order - try l1_7683.open(order) { - bytes32 id = OrderEncoder.id(orderData); - orderIds[i] = id; - batchSuccesses++; - totalSuccesses++; - console2.log("Transaction %d succeeded with nonce %d, Order ID: %s", i, nonceCounter, vm.toString(id)); - emit TransactionSent(id, batchNumber, true); - nonceCounter++; // Increment nonce only on success - } catch Error(string memory reason) { - console2.log("Transaction %d failed with nonce %d: %s", i, nonceCounter, reason); - batchFailures++; - totalFailures++; - orderIds[i] = bytes32(0); - emit TransactionSent(bytes32(0), batchNumber, false); - } catch { - console2.log("Transaction %d failed with nonce %d with unknown error", i, nonceCounter); - batchFailures++; - totalFailures++; - orderIds[i] = bytes32(0); - emit TransactionSent(bytes32(0), batchNumber, false); - } - - vm.stopBroadcast(); - totalTransactions++; - - // Wait for transaction confirmation or add delay - vm.sleep(500); // 500ms delay - } - - // Record batch results - batchResults.push( - BatchResult({ - batchNumber: batchNumber, - timestamp: 0, - successCount: batchSuccesses, - failureCount: batchFailures, - orderIds: orderIds - }) - ); - console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); - emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); - } - - function _executeBatchBaseToArbitrum( - T1ERC7683 l1_7683, - uint256 alicePk, - address alice, - uint256 batchNumber - ) - internal - { - console2.log("=== Executing Batch %d ===", batchNumber); - bytes32[] memory orderIds = new bytes32[](currentSession.transactionsPerBatch); - uint256 batchSuccesses = 0; - uint256 batchFailures = 0; - - for (uint256 i = 0; i < currentSession.transactionsPerBatch; i++) { - // Find a valid (unused) nonce - uint256 maxAttempts = type(uint256).max; // Safety limit to prevent infinite loop - uint256 attempts = 0; - while ( - l1_7683.usedNonces(alice, nonceCounter) - || !l1_7683.isValidNonce(alice, nonceCounter) && attempts < maxAttempts - ) { - nonceCounter++; - attempts++; - } - if (attempts >= maxAttempts) { - console2.log("Transaction %d failed: Could not find valid nonce", i); - batchFailures++; - totalFailures++; - continue; - } - - vm.startBroadcast(alicePk); - ERC20 inputToken = ERC20(vm.envAddress("BASE_SEPOLIA_USDT_ADDR")); - ERC20 outputToken = ERC20(vm.envAddress("ARBITRUM_SEPOLIA_USDT_ADDR")); - - // Log allowance and balance - uint256 allowance = inputToken.allowance(alice, address(l1_7683)); - uint256 balance = inputToken.balanceOf(alice); - console2.log("Transaction %d: Allowance: %d, Balance: %d", i, allowance, balance); - - // Log Ethereum nonce - uint256 ethNonce = vm.getNonce(alice); - console2.log("Transaction %d: Ethereum nonce: %d, SenderNonce: %d", i, ethNonce, nonceCounter); - - // Prepare order data - OrderData memory orderData = OrderData({ - sender: TypeCasts.addressToBytes32(alice), - recipient: TypeCasts.addressToBytes32(alice), - inputToken: TypeCasts.addressToBytes32(address(inputToken)), - outputToken: TypeCasts.addressToBytes32(address(outputToken)), - amountIn: AMOUNT_IN, - minAmountOut: AMOUNT_IN * 9 / 10, - senderNonce: uint32(nonceCounter), - originDomain: uint32(T1Constants.BASE_SEPOLIA_CHAIN_ID), - destinationDomain: uint32(T1Constants.ARBITRUM_SEPOLIA_CHAIN_ID), - destinationSettler: TypeCasts.addressToBytes32(vm.envAddress("ARB_T1_PULL_BASED_7683_PROXY_ADDR")), - fillDeadline: uint32(1800), // 30 minutes - closedAuction: true, - data: new bytes(0) - }); - - bytes memory encodedOrder = OrderEncoder.encode(orderData); - OnchainCrossChainOrder memory order = - _prepareOnchainOrder(encodedOrder, uint32(1800), OrderEncoder.orderDataType()); - - // Try submitting the order - try l1_7683.open(order) { - bytes32 id = OrderEncoder.id(orderData); - orderIds[i] = id; - batchSuccesses++; - totalSuccesses++; - console2.log("Transaction %d succeeded with nonce %d, Order ID: %s", i, nonceCounter, vm.toString(id)); - emit TransactionSent(id, batchNumber, true); - nonceCounter++; // Increment nonce only on success - } catch Error(string memory reason) { - console2.log("Transaction %d failed with nonce %d: %s", i, nonceCounter, reason); - batchFailures++; - totalFailures++; - orderIds[i] = bytes32(0); - emit TransactionSent(bytes32(0), batchNumber, false); - } catch { - console2.log("Transaction %d failed with nonce %d with unknown error", i, nonceCounter); - batchFailures++; - totalFailures++; - orderIds[i] = bytes32(0); - emit TransactionSent(bytes32(0), batchNumber, false); - } - - vm.stopBroadcast(); - totalTransactions++; - - // Wait for transaction confirmation or add delay - vm.sleep(500); // 500ms delay - } - - // Record batch results - batchResults.push( - BatchResult({ - batchNumber: batchNumber, - timestamp: 0, - successCount: batchSuccesses, - failureCount: batchFailures, - orderIds: orderIds - }) - ); - console2.log("Batch %d completed: %d successes, %d failures", batchNumber, batchSuccesses, batchFailures); - emit BatchCompleted(batchNumber, batchSuccesses, batchFailures); - } - - function _prepareOnchainOrder( - bytes memory orderData, - uint32 fillDeadline, - bytes32 orderDataType - ) - internal - pure - returns (OnchainCrossChainOrder memory) - { - return - OnchainCrossChainOrder({ fillDeadline: fillDeadline, orderDataType: orderDataType, orderData: orderData }); - } - - // Utility functions for analysis - function getBatchResults() external view returns (BatchResult[] memory) { - return batchResults; - } - - function getCurrentSession() external view returns (TestSession memory) { - return currentSession; - } - - function getOverallSuccessRate() external view returns (uint256) { - if (totalTransactions == 0) return 0; - return (totalSuccesses * 100) / totalTransactions; - } - - function getAverageBatchSuccessRate() external view returns (uint256) { - if (batchResults.length == 0) return 0; - uint256 totalBatchSuccesses = 0; - uint256 totalBatchTransactions = 0; - for (uint256 i = 0; i < batchResults.length; i++) { - totalBatchSuccesses += batchResults[i].successCount; - totalBatchTransactions += batchResults[i].successCount + batchResults[i].failureCount; - } - if (totalBatchTransactions == 0) return 0; - return (totalBatchSuccesses * 100) / totalBatchTransactions; - } - - // Emergency stop function - function emergencyStop() external { - require(currentSession.isActive, "No active session to stop"); - currentSession.isActive = false; - console2.log("Emergency stop triggered - session ended"); - } -} diff --git a/contracts/script/test/7683/loadtesting/loadtest.sh b/contracts/script/test/7683/loadtesting/loadtest.sh new file mode 100755 index 00000000..2052c566 --- /dev/null +++ b/contracts/script/test/7683/loadtesting/loadtest.sh @@ -0,0 +1,312 @@ +#!/bin/bash +# =============================================== +# T1ERC7683 Intent Settlement Script +# Random asset selection + timed transactions + random direction (Base <-> Arbitrum) +# Uses random uint32 nonce without validation, with retries for nonce collisions +# Limits transaction amounts to $0.001 USD equivalent for USDC and WETH +# Uses environment variables for chain-specific configs +# Matches Solidity OrderData struct with data field +# =============================================== +# Suppress Foundry nightly warning +export FOUNDRY_DISABLE_NIGHTLY_WARNING=1 +# Load environment variables from .env file if it exists +if [[ -f ".env" ]]; then + source .env +fi +# CONFIGURATION +# Ensure required env vars are set +required_vars=("RPC_BASE" "RPC_ARB" "PROXY_BASE" "PROXY_ARB" "PRIVATE_KEY" "SENDER_ADDRESS" "RECIPIENT_ADDRESS") +for var in "${required_vars[@]}"; do + if [[ -z "${!var}" ]]; then + echo "❌ Error: $var environment variable is not set" >&2 + exit 1 + fi +done +# Set default token addresses if not provided +USDC_BASE="${USDC_BASE:-0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913}" +USDC_ARB="${USDC_ARB:-0xaf88d065e77c8cC2239327C5EDb3A432268e5831}" +WETH_BASE="${WETH_BASE:-0x4200000000000000000000000000000000000006}" +WETH_ARB="${WETH_ARB:-0x82aF49447D8a07e3bd95BD0d56f35241523fBab1}" +# Chain domains (fixed) +DOMAIN_BASE=8453 +DOMAIN_ARB=42161 +# Counterparts (counterpart on the other chain) +COUNTERPART_BASE="${COUNTERPART_BASE:-$PROXY_ARB}" # Default to Arb proxy +COUNTERPART_ARB="${COUNTERPART_ARB:-$PROXY_BASE}" # Default to Base proxy +# ASSETS CONFIG (parallel arrays for asset names and amounts) +ASSET_NAMES=("USDC" "WETH") +ASSET_AMOUNTS=("1000" "400000000000") # 0.001 USDC (6 decimals, ~$0.001), 0.0000004 WETH (18 decimals, ~$0.001 at $2500/WETH) +ASSET_DECIMALS=("6" "18") # Decimals for each asset +# ORDER ENCODING +FILL_DEADLINE=1800 # 30 minutes, fixed to match Solidity +# Corrected ORDER_DATA_TYPE without field names +ORDER_DATA_TYPE=$(cast keccak "OrderData(bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256,uint32,uint32,bytes32,uint32,bool,bytes)" | sed 's/^0x//') +ORDER_DATA_TYPE="0x$ORDER_DATA_TYPE" +# Validate ORDER_DATA_TYPE +if [[ ! "$ORDER_DATA_TYPE" =~ ^0x[0-9a-fA-F]{64}$ ]]; then + echo "❌ Error: Invalid ORDER_DATA_TYPE: $ORDER_DATA_TYPE" >&2 + exit 1 +fi +echo "🔍 ORDER_DATA_TYPE: $ORDER_DATA_TYPE" >&2 +encode_order_data() { + local asset=$1 + local amount=$2 + local decimals=$3 + local origin_domain=$4 + local dest_domain=$5 + local counterpart=$6 + local nonce=$7 + local input_token_addr + local output_token_addr + case $asset in + "USDC") + if [[ $origin_domain -eq $DOMAIN_BASE ]]; then + input_token_addr="$USDC_BASE" + else + input_token_addr="$USDC_ARB" + fi + if [[ $dest_domain -eq $DOMAIN_BASE ]]; then + output_token_addr="$USDC_BASE" + else + output_token_addr="$USDC_ARB" + fi + ;; + "WETH") + if [[ $origin_domain -eq $DOMAIN_BASE ]]; then + input_token_addr="$WETH_BASE" + else + input_token_addr="$WETH_ARB" + fi + if [[ $dest_domain -eq $DOMAIN_BASE ]]; then + output_token_addr="$WETH_BASE" + else + output_token_addr="$WETH_ARB" + fi + ;; + *) + echo "❌ Invalid asset: $asset" >&2 + return 1 + ;; + esac + if [[ -z "$input_token_addr" || -z "$output_token_addr" ]]; then + echo "❌ Error: Token addresses not set for $asset on origin $origin_domain -> dest $dest_domain" >&2 + return 1 + fi + local sender_addr=$(cast to-bytes32 "$SENDER_ADDRESS") + local recipient_addr=$(cast to-bytes32 "$RECIPIENT_ADDRESS") + local counterpart_addr=$(cast to-bytes32 "$counterpart") + local input_token=$(cast to-bytes32 "$input_token_addr") + local output_token=$(cast to-bytes32 "$output_token_addr") + local min_amount_out=$((amount * 90 / 100)) # Match Solidity: 90% + # Validate sender address matches private key + local derived_sender=$(cast wallet address --private-key "$PRIVATE_KEY") + if [[ "$derived_sender" != "$SENDER_ADDRESS" ]]; then + echo "❌ Error: SENDER_ADDRESS ($SENDER_ADDRESS) does not match address derived from PRIVATE_KEY ($derived_sender)" >&2 + return 1 + fi + # Validate counterpart address matches contract's counterpart + local contract_counterpart + if [[ $origin_domain -eq $DOMAIN_BASE ]]; then + contract_counterpart=$(cast call "$PROXY_BASE" "counterpart()(address)" --rpc-url "$RPC_BASE" 2>/dev/null) + else + contract_counterpart=$(cast call "$PROXY_ARB" "counterpart()(address)" --rpc-url "$RPC_ARB" 2>/dev/null) + fi + if [[ "$contract_counterpart" != "$counterpart" ]]; then + echo "❌ Error: Counterpart ($counterpart) does not match contract's counterpart ($contract_counterpart) on origin $origin_domain" >&2 + return 1 + fi + # Encode fields in the correct order per OrderData struct + local args=( + "$sender_addr" # bytes32 sender + "$recipient_addr" # bytes32 recipient + "$input_token" # bytes32 inputToken + "$output_token" # bytes32 outputToken + "$amount" # uint256 amountIn + "$min_amount_out" # uint256 minAmountOut + "$nonce" # uint256 senderNonce + "$origin_domain" # uint32 originDomain + "$dest_domain" # uint32 destinationDomain + "$counterpart_addr" # bytes32 destinationSettler + "$FILL_DEADLINE" # uint32 fillDeadline + "true" # bool closedAuction + "0x" # bytes data + ) + for i in "${!args[@]}"; do + if [[ -z "${args[$i]}" ]]; then + echo "❌ Error: Argument $((i+1)) is empty or invalid" >&2 + return 1 + fi + done + echo "🔍 Encoding arguments: ${args[*]}" >&2 + # ABI-encode with the correct type string + local encoded=$(cast abi-encode "encode(bytes32,bytes32,bytes32,bytes32,uint256,uint256,uint256,uint32,uint32,bytes32,uint32,bool,bytes)" "${args[@]}" 2>/dev/null) + if [[ $? -ne 0 ]]; then + echo "❌ Error: Failed to ABI encode arguments: ${args[*]}" >&2 + return 1 + fi + # Validate hex string + if [[ ! "$encoded" =~ ^0x[0-9a-fA-F]*$ ]]; then + echo "❌ Error: Invalid hex string in ORDER_DATA: $encoded" >&2 + return 1 + fi + local hex_length=${#encoded} + echo "🔍 ORDER_DATA length: $((hex_length - 2)) bytes" >&2 + echo "$encoded" + return 0 +} +# Get random uint32 nonce +get_valid_nonce() { + # Try od first + local nonce=$(od -An -tu4 -N4 /dev/urandom 2>/dev/null) + if [[ -z "$nonce" || ! "$nonce" =~ ^[0-9]+$ || "$nonce" -gt 4294967295 ]]; then + echo "⚠️ od failed, falling back to RANDOM" >&2 + # Fallback to RANDOM + nonce=$(( ($RANDOM << 16) + $RANDOM )) + if [[ -z "$nonce" || ! "$nonce" =~ ^[0-9]+$ || "$nonce" -gt 4294967295 ]]; then + echo "❌ Failed to generate random nonce with RANDOM" >&2 + return 1 + fi + fi + echo "🔍 Using random nonce: $nonce" >&2 + echo "$nonce" + return 0 +} +# MAIN SCRIPT +NUM_TXS="$1" +if [[ -z "$NUM_TXS" || ! "$NUM_TXS" =~ ^[0-9]+$ || "$NUM_TXS" -le 0 ]]; then + echo "❌ Error: NUM_TXS must be a positive integer (got '$NUM_TXS')" >&2 + exit 1 +fi +INTERVAL="${2:-0}" # Default: 0 (all at once) +if [[ ! "$INTERVAL" =~ ^[0-9]+$ ]]; then + echo "❌ Error: INTERVAL must be a non-negative integer (got '$INTERVAL')" >&2 + exit 1 +fi +echo "🚀 Starting $NUM_TXS transactions (interval: ${INTERVAL}s)" +echo " Base: RPC $RPC_BASE | Proxy $PROXY_BASE | Counterpart $COUNTERPART_BASE | Domain $DOMAIN_BASE" +echo " Arb: RPC $RPC_ARB | Proxy $PROXY_ARB | Counterpart $COUNTERPART_ARB | Domain $DOMAIN_ARB" +echo " Sender: $SENDER_ADDRESS" +echo "================================================================" +# Validate asset arrays +if [[ ${#ASSET_NAMES[@]} -eq 0 || ${#ASSET_NAMES[@]} -ne ${#ASSET_AMOUNTS[@]} || ${#ASSET_NAMES[@]} -ne ${#ASSET_DECIMALS[@]} ]]; then + echo "❌ Error: Asset arrays are misconfigured" >&2 + exit 1 +fi +echo "📋 Available assets: ${ASSET_NAMES[*]}" +# Check sender's balances +for chain in "BASE" "ARB"; do + RPC_VAR="RPC_$chain" + USDC_VAR="USDC_$chain" + WETH_VAR="WETH_$chain" + RPC="${!RPC_VAR}" + USDC="${!USDC_VAR}" + WETH="${!WETH_VAR}" + echo "🔍 Checking USDC balance for $SENDER_ADDRESS on $chain..." + USDC_BALANCE=$(cast call "$USDC" "balanceOf(address)(uint256)" "$SENDER_ADDRESS" --rpc-url "$RPC" 2>/dev/null | sed 's/ \[.*\]//') + if [[ $? -ne 0 || -z "$USDC_BALANCE" ]]; then + echo "⚠️ Failed to query USDC balance on $chain" >&2 + continue + fi + USDC_REQUIRED_AMOUNT="1000" # 0.001 USDC + if [[ ! "$USDC_BALANCE" =~ ^[0-9]+$ || "$USDC_BALANCE" -lt "$USDC_REQUIRED_AMOUNT" ]]; then + echo "❌ Insufficient USDC balance on $chain: $USDC_BALANCE < $USDC_REQUIRED_AMOUNT" >&2 + exit 1 + fi + echo "✅ USDC balance on $chain: $USDC_BALANCE (sufficient)" + echo "🔍 Checking WETH balance for $SENDER_ADDRESS on $chain..." + WETH_BALANCE=$(cast call "$WETH" "balanceOf(address)(uint256)" "$SENDER_ADDRESS" --rpc-url "$RPC" 2>/dev/null | sed 's/ \[.*\]//') + if [[ $? -ne 0 || -z "$WETH_BALANCE" ]]; then + echo "⚠️ Failed to query WETH balance on $chain" >&2 + continue + fi + WETH_REQUIRED_AMOUNT="400000000000" # 0.0000004 WETH + if [[ ! "$WETH_BALANCE" =~ ^[0-9]+$ || "$WETH_BALANCE" -lt "$WETH_REQUIRED_AMOUNT" ]]; then + echo "❌ Insufficient WETH balance on $chain: $WETH_BALANCE < $WETH_REQUIRED_AMOUNT" >&2 + exit 1 + fi + echo "✅ WETH balance on $chain: $WETH_BALANCE (sufficient)" +done +# Execute transactions +for ((i=1; i<=NUM_TXS; i++)); do + # Randomly select direction (0: Base -> Arb, 1: Arb -> Base) + DIRECTION=$((RANDOM % 2)) + if [[ $DIRECTION -eq 0 ]]; then + ORIGIN="BASE" + DEST="ARB" + else + ORIGIN="ARB" + DEST="BASE" + fi + RPC_VAR="RPC_$ORIGIN" + RPC="${!RPC_VAR}" + PROXY_VAR="PROXY_$ORIGIN" + PROXY="${!PROXY_VAR}" + DOMAIN_VAR="DOMAIN_$ORIGIN" + LOCAL_DOMAIN="${!DOMAIN_VAR}" + DEST_DOMAIN_VAR="DOMAIN_$DEST" + DEST_DOMAIN="${!DEST_DOMAIN_VAR}" + COUNTERPART_VAR="COUNTERPART_$ORIGIN" + COUNTERPART="${!COUNTERPART_VAR}" + # Randomly select asset + RANDOM_INDEX=$((RANDOM % ${#ASSET_NAMES[@]})) + ASSET="${ASSET_NAMES[$RANDOM_INDEX]}" + AMOUNT="${ASSET_AMOUNTS[$RANDOM_INDEX]}" + DECIMALS="${ASSET_DECIMALS[$RANDOM_INDEX]}" + # Approve ERC20 token for the selected asset on the origin chain + TOKEN_ADDR_VAR="${ASSET}_$ORIGIN" + TOKEN_ADDR="${!TOKEN_ADDR_VAR}" + if [[ -z "$TOKEN_ADDR" ]]; then + echo "❌ Error: Token address for $ASSET on $ORIGIN is not set" >&2 + continue + fi + echo "✅ Approving $ASSET ($TOKEN_ADDR) for $AMOUNT on $ORIGIN..." + cast send "$TOKEN_ADDR" "approve(address,uint256)" "$PROXY" "$AMOUNT" \ + --rpc-url "$RPC" --private-key "$PRIVATE_KEY" --gas-limit 100000 2>/dev/null || { + echo "⚠️ Approval for $ASSET on $ORIGIN failed (may already be approved or insufficient balance)" >&2 + } + # Try transaction with up to 3 nonce attempts + nonce_attempts=0 + max_nonce_attempts=3 + while [[ $nonce_attempts -lt $max_nonce_attempts ]]; do + # Get random nonce + NONCE=$(get_valid_nonce) + if [[ $? -ne 0 ]]; then + echo "❌ Skipping TX #$i due to nonce query failure" >&2 + break + fi + echo "📜 Using nonce: $NONCE on $ORIGIN (attempt $((nonce_attempts + 1))/$max_nonce_attempts)" + # Encode order + ORDER_DATA=$(encode_order_data "$ASSET" "$AMOUNT" "$DECIMALS" "$LOCAL_DOMAIN" "$DEST_DOMAIN" "$COUNTERPART" "$NONCE") + if [[ $? -ne 0 ]]; then + echo "❌ Skipping TX #$i due to order encoding failure" >&2 + break + fi + # Construct the STRUCT tuple as a string + STRUCT="($FILL_DEADLINE,$ORDER_DATA_TYPE,$ORDER_DATA)" + echo "🔍 Struct: $STRUCT" >&2 + echo "" + echo "📦 TX #$i/$NUM_TXS | Direction: $ORIGIN -> $DEST | Asset: $ASSET | Amount: $AMOUNT | Nonce: $NONCE" + # ERC20 transaction (USDC or WETH) + TX_RESULT=$(cast send "$PROXY" "open((uint32,bytes32,bytes))" "$STRUCT" \ + --rpc-url "$RPC" --private-key "$PRIVATE_KEY" --gas-limit 500000 2>&1) + if [[ $? -eq 0 ]]; then + echo "✅ TX #$i submitted!" + break + else + echo "❌ TX #$i failed: $TX_RESULT" >&2 + nonce_attempts=$((nonce_attempts + 1)) + if [[ $nonce_attempts -eq $max_nonce_attempts ]]; then + echo "❌ Skipping TX #$i after $max_nonce_attempts failed attempts" >&2 + break + fi + echo "⚠️ Retrying with new nonce..." >&2 + fi + done + # Wait interval (skip for last tx) + if [[ $i -lt $NUM_TXS && $INTERVAL -gt 0 ]]; then + echo "⏳ Waiting $INTERVAL seconds..." + sleep "$INTERVAL" + fi +done +echo "" +echo "🎉 Completed $NUM_TXS transactions!" \ No newline at end of file diff --git a/contracts/script/test/7683/run-load-test.sh b/contracts/script/test/7683/run-load-test.sh deleted file mode 100755 index 55b3112c..00000000 --- a/contracts/script/test/7683/run-load-test.sh +++ /dev/null @@ -1,241 +0,0 @@ -#!/bin/bash - -# 7683 Load Test Runner Script -# Usage: -# Burst Mode: ./run-load-test.sh burst [max_transactions] [direction] -# Sustained Mode: ./run-load-test.sh sustained [txs_per_batch] [interval] [duration] [direction] -# -# Examples: -# ./run-load-test.sh burst 50 arbitrum_to_base -# ./run-load-test.sh sustained 5 2m 20m base_to_arbitrum -# ./run-load-test.sh sustained 3 30s 5m arbitrum_to_base -# ./run-load-test.sh burst 25 # defaults to arbitrum_to_base - -# Load environment variables from .env file -load_env() { - # Look for .env file in current directory, then parent directories - local env_file="" - local current_dir="$(pwd)" - - # Check current directory first - if [ -f ".env" ]; then - env_file=".env" - # Check contracts directory (if running from script directory) - elif [ -f "../.env" ]; then - env_file="../.env" - # Check root directory (if running from contracts/script/test/7683) - elif [ -f "../../../.env" ]; then - env_file="../../../.env" - fi - - if [ -n "$env_file" ]; then - echo "Loading environment variables from: $env_file" - # Export variables from .env file, ignoring comments and empty lines - set -a # automatically export all variables - source "$env_file" - set +a # stop automatically exporting - echo "Environment variables loaded successfully" - else - echo "Warning: No .env file found in current directory or parent directories" - echo "Make sure you have a .env file with required variables" - fi -} - -# Function to parse time units (e.g., "30s", "2m", "1h") -parse_time() { - local time_str="$1" - local time_value="${time_str%[smh]}" - local time_unit="${time_str: -1}" - - case "$time_unit" in - s|S) - echo "$time_value" - ;; - m|M) - echo $((time_value * 60)) - ;; - h|H) - echo $((time_value * 3600)) - ;; - *) - # If no unit specified, assume seconds - echo "$time_value" - ;; - esac -} - -# Function to display usage -show_usage() { - echo "Usage:" - echo " Burst Mode: $0 burst [max_transactions] [direction]" - echo " Sustained Mode: $0 sustained [txs_per_batch] [interval] [duration] [direction]" - echo "" - echo "Examples:" - echo " $0 burst 50 arbitrum_to_base" - echo " $0 sustained 5 2m 20m base_to_arbitrum" - echo " $0 sustained 3 30s 5m arbitrum_to_base" - echo " $0 sustained 10 1h 2h arbitrum_to_base" - echo " $0 burst 25 # defaults to arbitrum_to_base" - echo "" - echo "Time units:" - echo " s = seconds (e.g., 30s)" - echo " m = minutes (e.g., 2m)" - echo " h = hours (e.g., 1h)" - echo "" - echo "Directions: arbitrum_to_base, base_to_arbitrum" -} - -# Load environment variables first -load_env - -# Check if at least one argument is provided -if [ $# -lt 1 ]; then - echo "Error: Missing test mode argument" - show_usage - exit 1 -fi - -MODE=$1 - -# Validate mode -if [[ "$MODE" != "burst" && "$MODE" != "sustained" ]]; then - echo "Error: Invalid mode. Use 'burst' or 'sustained'" - show_usage - exit 1 -fi - -# Set default RPC URL based on direction (will be overridden if direction is provided) -DEFAULT_RPC="arbitrum_sepolia" - -if [ "$MODE" = "burst" ]; then - # Burst mode configuration - MAX_TRANSACTIONS=${2:-10} - DIRECTION=${3:-arbitrum_to_base} - - # Validate direction - if [[ "$DIRECTION" != "arbitrum_to_base" && "$DIRECTION" != "base_to_arbitrum" ]]; then - echo "Error: Invalid direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'" - exit 1 - fi - - # Validate max transactions - if ! [[ "$MAX_TRANSACTIONS" =~ ^[0-9]+$ ]] || [ "$MAX_TRANSACTIONS" -lt 1 ]; then - echo "Error: Max transactions must be a positive integer" - exit 1 - fi - - # Set RPC URL based on direction - if [[ "$DIRECTION" == "arbitrum_to_base" ]]; then - RPC_URL="arbitrum_sepolia" - VERIFIER_URL="https://api-sepolia.arbiscan.io/api" - API_KEY="$ARBISCAN_API_KEY" - else - RPC_URL="base_sepolia" - VERIFIER_URL="https://api-sepolia.basescan.org/api" - API_KEY="$BASESCAN_API_KEY" - fi - - echo "==========================================" - echo "Starting 7683 BURST Load Test" - echo "==========================================" - echo "Mode: Burst" - echo "Max Transactions: $MAX_TRANSACTIONS" - echo "Direction: $DIRECTION" - echo "RPC URL: $RPC_URL" - echo "Timestamp: $(date)" - echo "==========================================" - - # Set environment variables for the test - export LOAD_TEST_MAX_TRANSACTIONS=$MAX_TRANSACTIONS - export LOAD_TEST_DIRECTION=$DIRECTION - - SCRIPT_NAME="BurstLoadTest" - -elif [ "$MODE" = "sustained" ]; then - # Sustained mode configuration - TXS_PER_BATCH=${2:-5} - INTERVAL_STR=${3:-1m} - DURATION_STR=${4:-10m} - DIRECTION=${5:-arbitrum_to_base} - - # Parse time strings to seconds - INTERVAL_SECONDS=$(parse_time "$INTERVAL_STR") - DURATION_SECONDS=$(parse_time "$DURATION_STR") - - # Validate direction - if [[ "$DIRECTION" != "arbitrum_to_base" && "$DIRECTION" != "base_to_arbitrum" ]]; then - echo "Error: Invalid direction. Use 'arbitrum_to_base' or 'base_to_arbitrum'" - exit 1 - fi - - # Validate numeric inputs - if ! [[ "$TXS_PER_BATCH" =~ ^[0-9]+$ ]] || [ "$TXS_PER_BATCH" -lt 1 ]; then - echo "Error: Transactions per batch must be a positive integer" - exit 1 - fi - - if ! [[ "$INTERVAL_SECONDS" =~ ^[0-9]+$ ]] || [ "$INTERVAL_SECONDS" -lt 1 ]; then - echo "Error: Invalid interval format. Use formats like '30s', '2m', '1h'" - exit 1 - fi - - if ! [[ "$DURATION_SECONDS" =~ ^[0-9]+$ ]] || [ "$DURATION_SECONDS" -lt 1 ]; then - echo "Error: Invalid duration format. Use formats like '30s', '2m', '1h'" - exit 1 - fi - - # Set RPC URL based on direction - if [[ "$DIRECTION" == "arbitrum_to_base" ]]; then - RPC_URL="arbitrum_sepolia" - VERIFIER_URL="https://api-sepolia.arbiscan.io/api" - API_KEY="$ARBISCAN_API_KEY" - else - RPC_URL="base_sepolia" - VERIFIER_URL="https://api-sepolia.basescan.org/api" - API_KEY="$BASESCAN_API_KEY" - fi - - echo "==========================================" - echo "Starting 7683 SUSTAINED Load Test" - echo "==========================================" - echo "Mode: Sustained" - echo "Transactions per batch: $TXS_PER_BATCH" - echo "Interval: $INTERVAL_STR ($INTERVAL_SECONDS seconds)" - echo "Duration: $DURATION_STR ($DURATION_SECONDS seconds)" - echo "Direction: $DIRECTION" - echo "RPC URL: $RPC_URL" - echo "Timestamp: $(date)" - echo "==========================================" - - # Set environment variables for the test - export SUSTAINED_TXS_PER_BATCH=$TXS_PER_BATCH - export SUSTAINED_INTERVAL_SECONDS=$INTERVAL_SECONDS - export SUSTAINED_DURATION_SECONDS=$DURATION_SECONDS - export LOAD_TEST_DIRECTION=$DIRECTION - - SCRIPT_NAME="SustainedLoadTest" -fi - -# Check if required API keys are set -if [ -z "$API_KEY" ]; then - echo "Error: Required API key is not set in environment" - if [[ "$DIRECTION" == "arbitrum_to_base" ]]; then - echo "Please set ARBISCAN_API_KEY environment variable" - else - echo "Please set BASESCAN_API_KEY environment variable" - fi - exit 1 -fi - -# Run the Forge script -cd "$(dirname "$0")/../../.." -forge script ./script/test/7683/${SCRIPT_NAME}.s.sol:${SCRIPT_NAME} \ - --rpc-url $RPC_URL \ - --broadcast \ - --ffi \ - --slow \ - --verbose - -echo "==========================================" -echo "Load test completed at: $(date)" -echo "=========================================="