harb/scripts/harb-evaluator/helpers/recenter.ts
openhands 9cda5beb4a fix: address review findings in market and recenter helpers
- recenter.ts: parse isUp from Recentered event logs instead of a
  follow-up eth_call that would decode wrong post-recenter state
- recenter.ts: remove hardcoded private key from comment; add blocks>0
  guard in mineBlocks; call provider.destroy() to prevent leaked intervals
- market.ts: snapshot KRK balance before buy to compute krkBought as
  delta instead of cumulative total; call provider.destroy() on exit;
  remove unused withdraw entry from WETH_ABI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 11:27:31 +00:00

86 lines
3.1 KiB
TypeScript

/**
* LiquidityManager recenter helper and Anvil block-mining utility.
*
* Pure Node.js — no browser/Playwright dependency.
*/
import { Interface, JsonRpcProvider, Wallet } from 'ethers';
import { rpcCall } from './rpc.js';
import { waitForReceipt } from './swap.js';
const RECENTER_ABI = [
'function recenter() external returns (bool isUp)',
'event Recentered(int24 indexed currentTick, bool indexed isUp)',
];
export interface RecenterConfig {
rpcUrl: string;
/** Address of the LiquidityManager contract */
lmAddress: string;
/**
* Private key of an account with recenter access.
* In the local Anvil stack, recenterAccess is granted to the deployer
* (Anvil account index 0) and the txnBot (Anvil account index 2).
* See scripts/bootstrap-common.sh for how access is granted at deploy time.
*/
privateKey: string;
/** Address corresponding to privateKey */
accountAddress: string;
}
/**
* Call LiquidityManager.recenter() from an authorized account.
*
* Reads isUp from the Recentered(int24 currentTick, bool isUp) event emitted
* by the mined transaction — not from a follow-up eth_call, which would
* simulate a second recenter on already-updated state and return the wrong value.
*
* @returns isUp - true if price moved up
*/
export async function triggerRecenter(config: RecenterConfig): Promise<boolean> {
const provider = new JsonRpcProvider(config.rpcUrl);
const wallet = new Wallet(config.privateKey, provider);
const iface = new Interface(RECENTER_ABI);
const data = iface.encodeFunctionData('recenter', []);
console.log('[recenter] Calling LiquidityManager.recenter()...');
const tx = await wallet.sendTransaction({ to: config.lmAddress, data });
await waitForReceipt(config.rpcUrl, tx.hash);
console.log(`[recenter] recenter() mined: ${tx.hash}`);
provider.destroy();
// Parse isUp from the Recentered event in the receipt logs.
// A follow-up eth_call would simulate on post-recenter state and give the wrong result.
const receipt = (await rpcCall(config.rpcUrl, 'eth_getTransactionReceipt', [tx.hash])) as {
logs: Array<{ address: string; topics: string[]; data: string }>;
};
const recenterEventTopic = iface.getEvent('Recentered')!.topicHash;
const log = receipt.logs.find(
l =>
l.address.toLowerCase() === config.lmAddress.toLowerCase() &&
l.topics[0] === recenterEventTopic,
);
if (!log) {
throw new Error('[recenter] Recentered event not found in receipt — did the tx revert silently?');
}
const parsed = iface.parseLog({ topics: log.topics, data: log.data });
const isUp = Boolean(parsed!.args.isUp);
console.log(`[recenter] isUp = ${isUp}`);
return isUp;
}
/**
* Mine `blocks` empty blocks on Anvil to advance time.
* Useful to get past MIN_RECENTER_INTERVAL (if set).
*
* Uses the anvil_mine RPC method.
*/
export async function mineBlocks(rpcUrl: string, blocks: number): Promise<void> {
if (blocks <= 0) throw new Error(`mineBlocks: blocks must be > 0, got ${blocks}`);
await rpcCall(rpcUrl, 'anvil_mine', ['0x' + blocks.toString(16)]);
console.log(`[recenter] Mined ${blocks} blocks`);
}