tax rate, version and compose (#70)

resolves #67

Co-authored-by: johba <johba@harb.eth>
Reviewed-on: https://codeberg.org/johba/harb/pulls/70
This commit is contained in:
johba 2025-10-07 19:26:08 +02:00
parent d8ca557eb6
commit 6cbb1781ce
40 changed files with 1243 additions and 213 deletions

106
scripts/sync-tax-rates.mjs Normal file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env node
/**
* Sync TAX_RATE options from the on-chain Stake contract to kraiken-lib.
*
* The Stake.sol contract defines the canonical TAX_RATES array in basis points (yearly percentage * 100).
* This script parses that array and regenerates kraiken-lib/src/taxRates.ts so that all front-end consumers
* share a single source of truth that mirrors the deployed contract.
*/
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const stakeSourcePath = path.resolve(__dirname, '../onchain/src/Stake.sol');
const taxRatesDestPath = path.resolve(__dirname, '../kraiken-lib/src/taxRates.ts');
const source = readFileSync(stakeSourcePath, 'utf8');
const match = source.match(/uint256\[]\s+public\s+TAX_RATES\s*=\s*\[([^\]]+)\]/m);
if (!match) {
throw new Error(`Unable to locate TAX_RATES array in ${stakeSourcePath}`);
}
const rawValues = match[1]
.split(',')
.map(value => value.trim())
.filter(Boolean);
if (rawValues.length === 0) {
throw new Error('TAX_RATES array is empty or failed to parse');
}
const taxRateOptions = rawValues.map((value, index) => {
const basisPoints = Number(value.replace(/_/g, ''));
if (!Number.isFinite(basisPoints)) {
throw new Error(`Invalid TAX_RATES entry "${value}" at index ${index}`);
}
const yearlyPercent = basisPoints;
const decimalRate = yearlyPercent / 100;
const dailyPercent = yearlyPercent / 365;
return {
index,
year: yearlyPercent,
daily: Number(dailyPercent.toFixed(5)),
decimal: Number(decimalRate.toFixed(2)),
};
});
// Generate checksum for runtime validation
import { createHash } from 'node:crypto';
const taxRatesString = rawValues.join(',');
const checksum = createHash('sha256').update(taxRatesString).digest('hex').slice(0, 16);
const fileHeader = `/**
* AUTO-GENERATED FILE DO NOT EDIT
*
* Generated by scripts/sync-tax-rates.mjs from onchain/src/Stake.sol.
* Run \`node scripts/sync-tax-rates.mjs\` after modifying the contract TAX_RATES array.
*/
`;
const interfaceDef = `export interface TaxRateOption {
index: number;
year: number;
daily: number;
decimal: number;
}
`;
const optionLines = taxRateOptions
.map(option => ` { index: ${option.index}, year: ${option.year}, daily: ${option.daily}, decimal: ${option.decimal} }`)
.join(',\n');
const content = `${fileHeader}
${interfaceDef}
export const TAX_RATE_OPTIONS: TaxRateOption[] = [
${optionLines}
];
/**
* Checksum of the contract TAX_RATES array (first 16 chars of SHA-256).
* Used for runtime validation to ensure kraiken-lib is in sync with deployed contracts.
*
* To validate at runtime:
* 1. Read TAX_RATES array from the Stake contract
* 2. Compute checksum: sha256(values.join(',')).slice(0, 16)
* 3. Compare with TAX_RATES_CHECKSUM
*
* If mismatch: Run \`node scripts/sync-tax-rates.mjs\` and rebuild kraiken-lib.
*/
export const TAX_RATES_CHECKSUM = '${checksum}';
/**
* Array of raw year values (matches contract uint256[] TAX_RATES exactly).
* Used for runtime validation without needing to parse options.
*/
export const TAX_RATES_RAW = [${rawValues.join(', ')}];
`;
writeFileSync(taxRatesDestPath, content.trimStart() + '\n');