// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "forge-std/Test.sol"; /** * @title ScenarioRecorder * @notice Records trading scenarios for exact replay and debugging * @dev Captures all actions, state changes, and parameters for deterministic reproduction */ contract ScenarioRecorder is Test { struct ScenarioMetadata { uint256 seed; string optimizer; uint256 initialEth; uint256 traderInitialEth; uint256 startTimestamp; uint256 startBlock; bool token0isWeth; uint24 poolFee; } struct Action { uint256 step; string actionType; // SETUP, BUY, SELL, RECENTER, STAKE, UNSTAKE uint256 timestamp; uint256 blockNumber; address actor; bytes data; // Encoded action-specific data } struct StateSnapshot { uint256 traderWeth; uint256 traderKraiken; int24 currentTick; uint256 poolPrice; uint256 vwap; uint256 outstandingSupply; } struct OptimizerSnapshot { uint256 capitalInefficiency; uint256 anchorShare; uint256 anchorWidth; uint256 discoveryDepth; } // Storage ScenarioMetadata public metadata; Action[] public actions; mapping(uint256 => StateSnapshot) public preStates; mapping(uint256 => StateSnapshot) public postStates; mapping(uint256 => OptimizerSnapshot) public optimizerStates; uint256 public currentStep; // Events for easier parsing event ActionRecorded(uint256 indexed step, string actionType, address actor); function initializeScenario( uint256 _seed, string memory _optimizer, uint256 _initialEth, uint256 _traderInitialEth, bool _token0isWeth, uint24 _poolFee ) external { metadata = ScenarioMetadata({ seed: _seed, optimizer: _optimizer, initialEth: _initialEth, traderInitialEth: _traderInitialEth, startTimestamp: block.timestamp, startBlock: block.number, token0isWeth: _token0isWeth, poolFee: _poolFee }); currentStep = 0; } function recordBuy( address trader, uint256 wethAmount, uint256 minKraikenOut, uint160 sqrtPriceLimitX96 ) external { bytes memory data = abi.encode(wethAmount, minKraikenOut, sqrtPriceLimitX96); _recordAction("BUY", trader, data); } function recordSell( address trader, uint256 kraikenAmount, uint256 minWethOut, uint160 sqrtPriceLimitX96 ) external { bytes memory data = abi.encode(kraikenAmount, minWethOut, sqrtPriceLimitX96); _recordAction("SELL", trader, data); } function recordRecenter( address caller, uint256 gasPrice ) external { bytes memory data = abi.encode(gasPrice); _recordAction("RECENTER", caller, data); } function recordPreState( uint256 traderWeth, uint256 traderKraiken, int24 currentTick, uint256 poolPrice, uint256 vwap, uint256 outstandingSupply ) external { preStates[currentStep] = StateSnapshot({ traderWeth: traderWeth, traderKraiken: traderKraiken, currentTick: currentTick, poolPrice: poolPrice, vwap: vwap, outstandingSupply: outstandingSupply }); } function recordPostState( uint256 traderWeth, uint256 traderKraiken, int24 currentTick, uint256 poolPrice, uint256 vwap, uint256 outstandingSupply ) external { postStates[currentStep] = StateSnapshot({ traderWeth: traderWeth, traderKraiken: traderKraiken, currentTick: currentTick, poolPrice: poolPrice, vwap: vwap, outstandingSupply: outstandingSupply }); } function recordOptimizerParams( uint256 capitalInefficiency, uint256 anchorShare, uint256 anchorWidth, uint256 discoveryDepth ) external { optimizerStates[currentStep] = OptimizerSnapshot({ capitalInefficiency: capitalInefficiency, anchorShare: anchorShare, anchorWidth: anchorWidth, discoveryDepth: discoveryDepth }); } function _recordAction( string memory actionType, address actor, bytes memory data ) private { actions.push(Action({ step: currentStep, actionType: actionType, timestamp: block.timestamp, blockNumber: block.number, actor: actor, data: data })); emit ActionRecorded(currentStep, actionType, actor); currentStep++; } /** * @notice Export scenario to JSON format * @dev This function generates JSON that can be written to file */ function exportToJson() external view returns (string memory) { string memory json = "{"; // Metadata json = string.concat(json, '"scenario":{'); json = string.concat(json, '"seed":', vm.toString(metadata.seed), ','); json = string.concat(json, '"optimizer":"', metadata.optimizer, '",'); json = string.concat(json, '"initialEth":"', vm.toString(metadata.initialEth), '",'); json = string.concat(json, '"traderInitialEth":"', vm.toString(metadata.traderInitialEth), '",'); json = string.concat(json, '"startTimestamp":', vm.toString(metadata.startTimestamp), ','); json = string.concat(json, '"startBlock":', vm.toString(metadata.startBlock), ','); json = string.concat(json, '"token0isWeth":', metadata.token0isWeth ? "true" : "false", ','); json = string.concat(json, '"poolFee":', vm.toString(metadata.poolFee)); json = string.concat(json, '},'); // Actions json = string.concat(json, '"actions":['); for (uint256 i = 0; i < actions.length; i++) { if (i > 0) json = string.concat(json, ','); json = string.concat(json, _actionToJson(i)); } json = string.concat(json, ']}'); return json; } function _actionToJson(uint256 index) private view returns (string memory) { Action memory action = actions[index]; string memory json = "{"; json = string.concat(json, '"step":', vm.toString(action.step), ','); json = string.concat(json, '"type":"', action.actionType, '",'); json = string.concat(json, '"timestamp":', vm.toString(action.timestamp), ','); json = string.concat(json, '"block":', vm.toString(action.blockNumber), ','); json = string.concat(json, '"actor":"', vm.toString(action.actor), '"'); // Add pre/post states if they exist if (preStates[index].poolPrice > 0) { json = string.concat(json, ',"preState":', _stateToJson(preStates[index])); } if (postStates[index].poolPrice > 0) { json = string.concat(json, ',"postState":', _stateToJson(postStates[index])); } json = string.concat(json, '}'); return json; } function _stateToJson(StateSnapshot memory state) private view returns (string memory) { string memory json = "{"; json = string.concat(json, '"traderWeth":"', vm.toString(state.traderWeth), '",'); json = string.concat(json, '"traderKraiken":"', vm.toString(state.traderKraiken), '",'); json = string.concat(json, '"currentTick":', vm.toString(state.currentTick), ','); json = string.concat(json, '"poolPrice":"', vm.toString(state.poolPrice), '",'); json = string.concat(json, '"vwap":"', vm.toString(state.vwap), '",'); json = string.concat(json, '"outstandingSupply":"', vm.toString(state.outstandingSupply), '"'); json = string.concat(json, '}'); return json; } }