fix: feat: Anvil snapshot/revert and ethPerToken helpers (#519)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
83e9a52b5b
commit
b2db3c7ae5
2 changed files with 115 additions and 0 deletions
36
scripts/harb-evaluator/helpers/anvil.ts
Normal file
36
scripts/harb-evaluator/helpers/anvil.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Anvil snapshot/revert helpers for the red-team agent feedback loop.
|
||||
*
|
||||
* snapshot() and revert() use Anvil's proprietary RPC methods to save and
|
||||
* restore chain state, allowing scenarios to run mutating actions and then
|
||||
* reset the fork cleanly.
|
||||
*
|
||||
* mineBlocks is re-exported from recenter.ts so callers can import both
|
||||
* snapshot helpers and block-mining from a single module.
|
||||
*/
|
||||
import { rpcCall } from './rpc.js';
|
||||
export { mineBlocks } from './recenter.js';
|
||||
|
||||
/**
|
||||
* Take an Anvil chain snapshot.
|
||||
*
|
||||
* @returns The snapshot ID (hex string) to pass to revert().
|
||||
*/
|
||||
export async function snapshot(rpcUrl: string): Promise<string> {
|
||||
const id = (await rpcCall(rpcUrl, 'anvil_snapshot', [])) as string;
|
||||
console.log(`[anvil] Snapshot taken: ${id}`);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the chain to a previously taken snapshot.
|
||||
*
|
||||
* Throws if Anvil reports the revert as unsuccessful (e.g. unknown snapshot ID).
|
||||
*
|
||||
* @param snapshotId - The hex snapshot ID returned by snapshot().
|
||||
*/
|
||||
export async function revert(rpcUrl: string, snapshotId: string): Promise<void> {
|
||||
const success = (await rpcCall(rpcUrl, 'anvil_revert', [snapshotId])) as boolean;
|
||||
if (!success) throw new Error(`[anvil] revert failed for snapshot ${snapshotId}`);
|
||||
console.log(`[anvil] Reverted to snapshot: ${snapshotId}`);
|
||||
}
|
||||
79
scripts/harb-evaluator/helpers/floor.ts
Normal file
79
scripts/harb-evaluator/helpers/floor.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Floor price helpers for the red-team agent feedback loop.
|
||||
*
|
||||
* Reads ethPerToken and related floor diagnostics from the Kraiken contract
|
||||
* and supporting on-chain state via direct JSON-RPC calls.
|
||||
*/
|
||||
import { Interface } from 'ethers';
|
||||
import { rpcCall } from './rpc.js';
|
||||
|
||||
// Base WETH address — stable across Anvil forks of Base Sepolia.
|
||||
const WETH = '0x4200000000000000000000000000000000000006';
|
||||
|
||||
const KRK_ABI = [
|
||||
'function ethPerToken() external view returns (uint256)',
|
||||
'function outstandingSupply() external view returns (uint256)',
|
||||
];
|
||||
const ERC20_ABI = ['function balanceOf(address account) external view returns (uint256)'];
|
||||
|
||||
const krkIface = new Interface(KRK_ABI);
|
||||
const erc20Iface = new Interface(ERC20_ABI);
|
||||
|
||||
/**
|
||||
* Read the current floor price from the Kraiken contract.
|
||||
*
|
||||
* @param krkAddress - Kraiken contract address.
|
||||
* @param _lmAddress - LiquidityManager address (reserved for future fallback computation).
|
||||
* @returns ethPerToken in wei.
|
||||
*/
|
||||
export async function getEthPerToken(rpcUrl: string, krkAddress: string, _lmAddress: string): Promise<bigint> {
|
||||
const calldata = krkIface.encodeFunctionData('ethPerToken', []);
|
||||
const result = (await rpcCall(rpcUrl, 'eth_call', [{ to: krkAddress, data: calldata }, 'latest'])) as string;
|
||||
const [value] = krkIface.decodeFunctionResult('ethPerToken', result);
|
||||
return BigInt(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read full floor diagnostics for the LiquidityManager / Kraiken pair.
|
||||
*
|
||||
* All four reads are issued in parallel to minimise round-trip latency.
|
||||
*
|
||||
* @param lmAddress - LiquidityManager contract address.
|
||||
* @param krkAddress - Kraiken contract address.
|
||||
* @returns {ethPerToken} floor price in wei
|
||||
* @returns {lmEthBalance} native ETH held by the LiquidityManager
|
||||
* @returns {lmWethBalance} WETH held by the LiquidityManager
|
||||
* @returns {outstandingSupply} total KRK outstanding supply
|
||||
*/
|
||||
export async function getFloorState(
|
||||
rpcUrl: string,
|
||||
lmAddress: string,
|
||||
krkAddress: string,
|
||||
): Promise<{
|
||||
ethPerToken: bigint;
|
||||
lmEthBalance: bigint;
|
||||
lmWethBalance: bigint;
|
||||
outstandingSupply: bigint;
|
||||
}> {
|
||||
const ethPerTokenCalldata = krkIface.encodeFunctionData('ethPerToken', []);
|
||||
const outstandingSupplyCalldata = krkIface.encodeFunctionData('outstandingSupply', []);
|
||||
const wethBalanceCalldata = erc20Iface.encodeFunctionData('balanceOf', [lmAddress]);
|
||||
|
||||
const [ethPerTokenHex, lmEthBalanceHex, lmWethBalanceHex, outstandingSupplyHex] = (await Promise.all([
|
||||
rpcCall(rpcUrl, 'eth_call', [{ to: krkAddress, data: ethPerTokenCalldata }, 'latest']),
|
||||
rpcCall(rpcUrl, 'eth_getBalance', [lmAddress, 'latest']),
|
||||
rpcCall(rpcUrl, 'eth_call', [{ to: WETH, data: wethBalanceCalldata }, 'latest']),
|
||||
rpcCall(rpcUrl, 'eth_call', [{ to: krkAddress, data: outstandingSupplyCalldata }, 'latest']),
|
||||
])) as [string, string, string, string];
|
||||
|
||||
const [ethPerToken] = krkIface.decodeFunctionResult('ethPerToken', ethPerTokenHex);
|
||||
const [lmWethBalance] = erc20Iface.decodeFunctionResult('balanceOf', lmWethBalanceHex);
|
||||
const [outstandingSupply] = krkIface.decodeFunctionResult('outstandingSupply', outstandingSupplyHex);
|
||||
|
||||
return {
|
||||
ethPerToken: BigInt(ethPerToken),
|
||||
lmEthBalance: BigInt(lmEthBalanceHex),
|
||||
lmWethBalance: BigInt(lmWethBalance),
|
||||
outstandingSupply: BigInt(outstandingSupply),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue