harb/onchain/analysis/helpers/ScenarioRecorder.sol

241 lines
8 KiB
Solidity
Raw Normal View History

// 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;
}
}