-
Notifications
You must be signed in to change notification settings - Fork 9
Load Testing for Intent Bridge #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 6 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
8f5b5fe
chore: boilerplate
rymcol f357f5d
fix: nonce issues in burst resolved
rymcol 20a25da
fix: nonce issues in sustained test
rymcol 141dc53
fix: forge fmt
rymcol 77ddf94
fix: prettier formatting for docs
rymcol 239f709
fix: minor text change for clarity
rymcol 2dbdee0
fix: address comments on logs
rymcol 491479a
Merge branch 'canary' of github.com:t1protocol/t1 into xCR/bridge-loa…
rymcol 5d1d748
fix: pr comments, stop batching
rymcol d524ef2
fix: limits
rymcol 127ece7
fix: load test subfoldering
rymcol 36dd42e
fix: extra ;
rymcol 8c3b7f5
fix: syntax
rymcol 7da2862
Revert "fix: load test subfoldering"
rymcol bbac22a
feat: bash load test
rymcol File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,267 @@ | ||
| // 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("=== 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 { | ||
| 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 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++; | ||
| 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(); | ||
| } | ||
|
|
||
| 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++; | ||
| 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(); | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.