harb/scripts/harb-evaluator/helpers/floor.ts
openhands b2db3c7ae5 fix: feat: Anvil snapshot/revert and ethPerToken helpers (#519)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 00:12:49 +00:00

79 lines
3.2 KiB
TypeScript

/**
* 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),
};
}