211 lines
No EOL
7.1 KiB
Bash
Executable file
211 lines
No EOL
7.1 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
BOLD='\033[1m'
|
|
|
|
# Check arguments
|
|
if [ $# -lt 1 ]; then
|
|
echo "Usage: $0 <RUN_ID> [seed_number]"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 241218-A7K9 # Replay all scenarios from run"
|
|
echo " $0 241218-A7K9 1 # Replay specific seed from run"
|
|
echo ""
|
|
echo "To find RUN_IDs, check fuzzing_results_recorded_* directories"
|
|
exit 1
|
|
fi
|
|
|
|
RUN_ID=$1
|
|
SEED=$2
|
|
|
|
echo -e "${GREEN}=== Scenario Replay Tool ===${NC}"
|
|
echo -e "Run ID: ${BOLD}${RUN_ID}${NC}"
|
|
|
|
# Find the results directory
|
|
RESULTS_DIR=$(find . -type d -name "*_*" 2>/dev/null | grep -E "fuzzing_results_recorded.*" | xargs -I {} sh -c 'ls -1 {}/scenario_'${RUN_ID}'_*.json 2>/dev/null && echo {}' | tail -1)
|
|
|
|
if [ -z "$RESULTS_DIR" ]; then
|
|
echo -e "${RED}Error: No results found for Run ID ${RUN_ID}${NC}"
|
|
echo ""
|
|
echo "Available Run IDs:"
|
|
find . -type f -name "scenario_*_seed*.json" 2>/dev/null | sed 's/.*scenario_\(.*\)_seed.*/\1/' | sort -u
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found results in: $RESULTS_DIR"
|
|
echo ""
|
|
|
|
# If no seed specified, show available scenarios
|
|
if [ -z "$SEED" ]; then
|
|
echo -e "${YELLOW}Available scenarios for ${RUN_ID}:${NC}"
|
|
for summary in $RESULTS_DIR/summary_${RUN_ID}_seed*.txt; do
|
|
if [ -f "$summary" ]; then
|
|
SEED_NUM=$(echo $summary | sed "s/.*seed\(.*\)\.txt/\1/")
|
|
echo ""
|
|
echo -e "${BOLD}Seed $SEED_NUM:${NC}"
|
|
grep -E "Profit:|Discovery Reached:" "$summary" | head -2
|
|
fi
|
|
done
|
|
echo ""
|
|
echo "To replay a specific scenario, run:"
|
|
echo " $0 ${RUN_ID} <seed_number>"
|
|
exit 0
|
|
fi
|
|
|
|
# Check if specific scenario files exist
|
|
SCENARIO_FILE="$RESULTS_DIR/scenario_${RUN_ID}_seed${SEED}.json"
|
|
REPLAY_FILE="$RESULTS_DIR/replay_${RUN_ID}_seed${SEED}.sol"
|
|
SUMMARY_FILE="$RESULTS_DIR/summary_${RUN_ID}_seed${SEED}.txt"
|
|
|
|
if [ ! -f "$SCENARIO_FILE" ]; then
|
|
echo -e "${RED}Error: Scenario file not found for seed ${SEED}${NC}"
|
|
echo "Looking for: $SCENARIO_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}=== Scenario Details ===${NC}"
|
|
if [ -f "$SUMMARY_FILE" ]; then
|
|
cat "$SUMMARY_FILE" | head -20
|
|
echo ""
|
|
fi
|
|
|
|
# Create replay test file
|
|
# Replace hyphens with underscores for valid Solidity identifier
|
|
CONTRACT_SAFE_ID=$(echo $RUN_ID | tr '-' '_')
|
|
REPLAY_TEST="test/Replay_${RUN_ID}_Seed${SEED}.t.sol"
|
|
|
|
echo -e "${YELLOW}Creating replay test file...${NC}"
|
|
cat > $REPLAY_TEST << 'EOF'
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "forge-std/Test.sol";
|
|
import {TestEnvironment} from "./helpers/TestBase.sol";
|
|
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
|
import {IWETH9} from "../src/interfaces/IWETH9.sol";
|
|
import {Kraiken} from "../src/Kraiken.sol";
|
|
import {Stake} from "../src/Stake.sol";
|
|
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
|
import "../analysis/helpers/SwapExecutor.sol";
|
|
import "../test/mocks/BullMarketOptimizer.sol";
|
|
|
|
contract Replay_CONTRACT_ID_Seed_SEED is Test {
|
|
TestEnvironment testEnv;
|
|
IUniswapV3Pool pool;
|
|
IWETH9 weth;
|
|
Kraiken kraiken;
|
|
Stake stake;
|
|
LiquidityManager lm;
|
|
bool token0isWeth;
|
|
|
|
address trader = makeAddr("trader");
|
|
address whale = makeAddr("whale");
|
|
address feeDestination = makeAddr("fees");
|
|
|
|
function setUp() public {
|
|
// Setup from recorded scenario
|
|
testEnv = new TestEnvironment(feeDestination);
|
|
BullMarketOptimizer optimizer = new BullMarketOptimizer();
|
|
|
|
(,pool, weth, kraiken, stake, lm,, token0isWeth) =
|
|
testEnv.setupEnvironmentWithOptimizer(SEED_PARITY, feeDestination, address(optimizer));
|
|
|
|
vm.deal(address(lm), 200 ether);
|
|
|
|
// Fund traders based on seed
|
|
uint256 traderFund = 50 ether + (uint256(keccak256(abi.encodePacked(uint256(SEED_NUM), "trader"))) % 150 ether);
|
|
uint256 whaleFund = 200 ether + (uint256(keccak256(abi.encodePacked(uint256(SEED_NUM), "whale"))) % 300 ether);
|
|
|
|
vm.deal(trader, traderFund * 2);
|
|
vm.prank(trader);
|
|
weth.deposit{value: traderFund}();
|
|
|
|
vm.deal(whale, whaleFund * 2);
|
|
vm.prank(whale);
|
|
weth.deposit{value: whaleFund}();
|
|
|
|
vm.prank(feeDestination);
|
|
lm.recenter();
|
|
}
|
|
|
|
function test_replay_CONTRACT_ID_seed_SEED() public {
|
|
console.log("=== Replaying Scenario RUN_ID Seed SEED ===");
|
|
|
|
uint256 initialBalance = weth.balanceOf(trader);
|
|
|
|
// INSERT_REPLAY_ACTIONS
|
|
|
|
uint256 finalBalance = weth.balanceOf(trader);
|
|
|
|
if (finalBalance > initialBalance) {
|
|
uint256 profit = finalBalance - initialBalance;
|
|
uint256 profitPct = (profit * 100) / initialBalance;
|
|
console.log(string.concat("[INVARIANT VIOLATED] Profit: ", vm.toString(profit / 1e18), " ETH (", vm.toString(profitPct), "%)"));
|
|
revert("Trader profited - invariant violated");
|
|
} else {
|
|
console.log("[OK] No profit");
|
|
}
|
|
}
|
|
|
|
function _executeBuy(address buyer, uint256 amount) internal {
|
|
if (weth.balanceOf(buyer) < amount) return;
|
|
SwapExecutor executor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm);
|
|
vm.prank(buyer);
|
|
weth.transfer(address(executor), amount);
|
|
try executor.executeBuy(amount, buyer) {} catch {}
|
|
}
|
|
|
|
function _executeSell(address seller, uint256 amount) internal {
|
|
if (kraiken.balanceOf(seller) < amount) {
|
|
amount = kraiken.balanceOf(seller);
|
|
if (amount == 0) return;
|
|
}
|
|
SwapExecutor executor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm);
|
|
vm.prank(seller);
|
|
kraiken.transfer(address(executor), amount);
|
|
try executor.executeSell(amount, seller) {} catch {}
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Replace placeholders
|
|
sed -i "s/CONTRACT_ID/${CONTRACT_SAFE_ID}/g" $REPLAY_TEST
|
|
sed -i "s/RUN_ID/${RUN_ID}/g" $REPLAY_TEST
|
|
sed -i "s/SEED_NUM/${SEED}/g" $REPLAY_TEST
|
|
sed -i "s/SEED_PARITY/$([ $((SEED % 2)) -eq 0 ] && echo "true" || echo "false")/g" $REPLAY_TEST
|
|
sed -i "s/_SEED/_${SEED}/g" $REPLAY_TEST
|
|
|
|
# Insert replay actions from the sol file
|
|
if [ -f "$REPLAY_FILE" ]; then
|
|
# Extract the function body from replay script
|
|
ACTIONS=$(sed -n '/function replayScenario/,/^}/p' "$REPLAY_FILE" | sed '1d;$d' | sed 's/^/ /')
|
|
|
|
# Use a temporary file for the replacement
|
|
awk -v actions="$ACTIONS" '/INSERT_REPLAY_ACTIONS/ {print actions; next} {print}' $REPLAY_TEST > ${REPLAY_TEST}.tmp
|
|
mv ${REPLAY_TEST}.tmp $REPLAY_TEST
|
|
fi
|
|
|
|
echo -e "${GREEN}Replay test created: $REPLAY_TEST${NC}"
|
|
echo ""
|
|
|
|
# Run the replay test
|
|
echo -e "${YELLOW}Running replay test...${NC}"
|
|
echo ""
|
|
|
|
forge test --match-contract Replay_${CONTRACT_SAFE_ID}_Seed_${SEED} -vv
|
|
|
|
echo ""
|
|
echo -e "${GREEN}=== Replay Complete ===${NC}"
|
|
echo ""
|
|
echo "To debug further:"
|
|
echo " 1. Edit the test file: $REPLAY_TEST"
|
|
echo " 2. Add console.log statements or assertions"
|
|
echo " 3. Run with more verbosity: forge test --match-contract Replay_${CONTRACT_SAFE_ID}_Seed_${SEED} -vvvv"
|
|
echo ""
|
|
echo "To visualize positions:"
|
|
echo " ./analysis/view-scenarios.sh $RESULTS_DIR" |