feat/ponder-lm-indexing (#142)

This commit is contained in:
johba 2026-02-18 00:19:05 +01:00
parent de3c8eef94
commit 31063379a8
107 changed files with 12517 additions and 367 deletions

View file

@ -0,0 +1,198 @@
/**
* Chain State Setup Script
*
* Prepares a realistic staking environment BEFORE tests run:
* 1. Funds test wallets (Anvil #3, #4, #5) with ETH + KRK
* 2. Creates active positions at different tax rates
* 3. Triggers a recenter to update pool state
* 4. Advances time to simulate position age
* 5. Takes chain snapshot for test resets
*/
import { ethers } from 'ethers';
import { readFileSync } from 'fs';
import { join } from 'path';
const RPC_URL = process.env.STACK_RPC_URL ?? 'http://localhost:8545';
// Anvil test accounts (private keys)
const DEPLOYER_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Anvil #0
const MARCUS_KEY = '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6'; // Anvil #3
const SARAH_KEY = '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a'; // Anvil #4
const PRIYA_KEY = '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'; // Anvil #5
interface ContractAddresses {
Kraiken: string;
Stake: string;
LiquidityManager: string;
}
async function loadContracts(): Promise<ContractAddresses> {
const deploymentsPath = join(process.cwd(), 'onchain', 'deployments-local.json');
const deploymentsJson = readFileSync(deploymentsPath, 'utf-8');
const deployments = JSON.parse(deploymentsJson);
return deployments.contracts;
}
async function sendRpc(method: string, params: unknown[]): Promise<any> {
const resp = await fetch(RPC_URL, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method, params }),
});
const data = await resp.json();
if (data.error) throw new Error(`RPC ${method} failed: ${data.error.message}`);
return data.result;
}
async function main() {
console.log('[SETUP] Starting chain state preparation...\n');
const provider = new ethers.JsonRpcProvider(RPC_URL);
const deployer = new ethers.Wallet(DEPLOYER_KEY, provider);
const marcus = new ethers.Wallet(MARCUS_KEY, provider);
const sarah = new ethers.Wallet(SARAH_KEY, provider);
const priya = new ethers.Wallet(PRIYA_KEY, provider);
const addresses = await loadContracts();
console.log('[SETUP] Contract addresses loaded:');
console.log(` - Kraiken: ${addresses.Kraiken}`);
console.log(` - Stake: ${addresses.Stake}`);
console.log(` - LiquidityManager: ${addresses.LiquidityManager}\n`);
// Contract ABIs (minimal required functions)
const krkAbi = [
'function transfer(address to, uint256 amount) returns (bool)',
'function balanceOf(address account) view returns (uint256)',
'function approve(address spender, uint256 amount) returns (bool)',
'function minStake() view returns (uint256)',
];
const stakeAbi = [
'function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch) returns (uint256)',
'function getPosition(uint256 positionId) view returns (tuple(uint256 share, address owner, uint32 creationTime, uint32 lastTaxTime, uint32 taxRate))',
// minStake() is on Kraiken, not Stake
'function nextPositionId() view returns (uint256)',
];
const lmAbi = [
'function recenter() external returns (bool)',
];
const krk = new ethers.Contract(addresses.Kraiken, krkAbi, deployer);
const stake = new ethers.Contract(addresses.Stake, stakeAbi, deployer);
const lm = new ethers.Contract(addresses.LiquidityManager, lmAbi, deployer);
// Step 1: Fund test wallets with ETH
console.log('[STEP 1] Funding test wallets with ETH...');
const ethAmount = ethers.parseEther('100'); // 100 ETH each
for (const [name, wallet] of [
['Marcus', marcus],
['Sarah', sarah],
['Priya', priya],
]) {
await sendRpc('anvil_setBalance', [wallet.address, '0x' + ethAmount.toString(16)]);
console.log(`${name} (${wallet.address}): 100 ETH`);
}
// Step 2: Transfer KRK from deployer to test wallets
console.log('\n[STEP 2] Distributing KRK tokens...');
const krkAmount = ethers.parseEther('1000'); // 1000 KRK each
for (const [name, wallet] of [
['Marcus', marcus],
['Sarah', sarah],
['Priya', priya],
]) {
const tx = await krk.transfer(wallet.address, krkAmount);
await tx.wait();
const balance = await krk.balanceOf(wallet.address);
console.log(`${name}: ${ethers.formatEther(balance)} KRK`);
}
// Step 3: Create staking positions
console.log('\n[STEP 3] Creating active staking positions...');
const minStake = await krk.minStake();
console.log(` Minimum stake: ${ethers.formatEther(minStake)} KRK`);
// Marcus stakes at LOW tax rate (index 2 = 5% yearly)
console.log('\n Creating Marcus position (LOW tax)...');
const marcusAmount = ethers.parseEther('300');
const marcusTaxRate = 2; // 5% yearly (0.0137% daily)
const marcusKrk = krk.connect(marcus) as typeof krk;
const marcusStake = stake.connect(marcus) as typeof stake;
let approveTx = await marcusKrk.approve(addresses.Stake, marcusAmount);
await approveTx.wait();
// Explicit nonce to avoid stale nonce cache
let nonce = await provider.getTransactionCount(marcus.address);
let snatchTx = await marcusStake.snatch(marcusAmount, marcus.address, marcusTaxRate, [], { nonce });
let receipt = await snatchTx.wait();
console.log(` ✓ Marcus position created (300 KRK @ 5% tax)`);
// Sarah stakes at MEDIUM tax rate (index 10 = 60% yearly)
console.log('\n Creating Sarah position (MEDIUM tax)...');
const sarahAmount = ethers.parseEther('500');
const sarahTaxRate = 10; // 60% yearly (0.1644% daily)
const sarahKrk = krk.connect(sarah) as typeof krk;
const sarahStake = stake.connect(sarah) as typeof stake;
approveTx = await sarahKrk.approve(addresses.Stake, sarahAmount);
await approveTx.wait();
nonce = await provider.getTransactionCount(sarah.address);
snatchTx = await sarahStake.snatch(sarahAmount, sarah.address, sarahTaxRate, [], { nonce });
receipt = await snatchTx.wait();
console.log(` ✓ Sarah position created (500 KRK @ 60% tax)`);
// Step 4: Trigger recenter via deployer
console.log('\n[STEP 4] Triggering recenter to update liquidity positions...');
try {
const recenterTx = await lm.recenter();
await recenterTx.wait();
console.log(' ✓ Recenter successful');
} catch (error: any) {
console.log(` ⚠ Recenter failed (may be expected): ${error.message}`);
}
// Step 5: Advance time by 1 day
console.log('\n[STEP 5] Advancing chain time by 1 day...');
const oneDay = 86400; // seconds
await sendRpc('anvil_increaseTime', [oneDay]);
await sendRpc('anvil_mine', [1]);
console.log(' ✓ Time advanced by 1 day');
// Step 6: Take chain snapshot
console.log('\n[STEP 6] Taking chain snapshot for test resets...');
const snapshotId = await sendRpc('evm_snapshot', []);
console.log(` ✓ Snapshot ID: ${snapshotId}`);
// Verify final state
console.log('\n[VERIFICATION] Final chain state:');
const nextPosId = await stake.nextPositionId();
console.log(` - Next position ID: ${nextPosId}`);
for (const [name, wallet] of [
['Marcus', marcus],
['Sarah', sarah],
['Priya', priya],
]) {
const balance = await krk.balanceOf(wallet.address);
console.log(` - ${name} KRK balance: ${ethers.formatEther(balance)} KRK`);
}
console.log('\n✅ Chain state setup complete!');
console.log(' Tests can now run against this prepared state.\n');
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error('\n❌ Setup failed:', error);
process.exit(1);
});