lint/lib (#49)
resolves #42 Co-authored-by: johba <johba@harb.eth> Reviewed-on: https://codeberg.org/johba/harb/pulls/49
This commit is contained in:
parent
4f7cebda56
commit
09c36f2c87
19 changed files with 2340 additions and 236 deletions
3
.husky/pre-commit
Executable file
3
.husky/pre-commit
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env sh
|
||||
cd kraiken-lib || exit 1
|
||||
npx lint-staged
|
||||
8
kraiken-lib/.prettierrc
Normal file
8
kraiken-lib/.prettierrc
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 140,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
29
kraiken-lib/eslint.config.js
Normal file
29
kraiken-lib/eslint.config.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ["src/**/*.ts"],
|
||||
ignores: ["src/tests/**/*", "src/__generated__/**/*"],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
parserOptions: { project: "./tsconfig.json" }
|
||||
},
|
||||
plugins: { "@typescript-eslint": tsPlugin },
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{ selector: "function", format: ["camelCase"] },
|
||||
{ selector: "variable", format: ["camelCase", "UPPER_CASE"] },
|
||||
{ selector: "typeLike", format: ["PascalCase"] }
|
||||
],
|
||||
"max-len": ["error", { code: 140, ignoreStrings: true, ignoreTemplateLiterals: true }],
|
||||
"no-console": "error",
|
||||
"semi": ["error", "always"],
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"prefer-const": "error"
|
||||
}
|
||||
}
|
||||
];
|
||||
1453
kraiken-lib/package-lock.json
generated
1453
kraiken-lib/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -5,37 +5,6 @@
|
|||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"compile": "graphql-codegen",
|
||||
"watch": "graphql-codegen -w"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.10",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-tag": "^2.12.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/client-preset": "^4.2.5",
|
||||
"@graphql-codegen/typescript": "^4.0.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.2.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^24.6.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.4.3"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
|
|
@ -77,5 +46,47 @@
|
|||
"require": "./dist/abis.js",
|
||||
"import": "./dist/abis.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"compile": "graphql-codegen",
|
||||
"watch": "graphql-codegen -w",
|
||||
"lint": "eslint 'src/**/*.ts'",
|
||||
"lint:fix": "eslint 'src/**/*.ts' --fix",
|
||||
"format": "prettier --write 'src/**/*.ts'",
|
||||
"format:check": "prettier --check 'src/**/*.ts'",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.ts": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.10",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-tag": "^2.12.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/client-preset": "^4.2.5",
|
||||
"@graphql-codegen/typescript": "^4.0.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.2.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^24.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
||||
"@typescript-eslint/parser": "^8.45.0",
|
||||
"eslint": "^9.36.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.7.0",
|
||||
"lint-staged": "^16.2.3",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
kraiken-lib/src/__generated__/graphql.ts
generated
18
kraiken-lib/src/__generated__/graphql.ts
generated
|
|
@ -7,14 +7,14 @@ export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> =
|
|||
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: { input: string; output: string; }
|
||||
String: { input: string; output: string; }
|
||||
Boolean: { input: boolean; output: boolean; }
|
||||
Int: { input: number; output: number; }
|
||||
Float: { input: number; output: number; }
|
||||
BigInt: { input: string; output: string; }
|
||||
ID: { input: string; output: string };
|
||||
String: { input: string; output: string };
|
||||
Boolean: { input: boolean; output: boolean };
|
||||
Int: { input: number; output: number };
|
||||
Float: { input: number; output: number };
|
||||
BigInt: { input: string; output: string };
|
||||
/** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
|
||||
JSON: { input: any; output: any; }
|
||||
JSON: { input: any; output: any };
|
||||
};
|
||||
|
||||
export type Meta = {
|
||||
|
|
@ -39,12 +39,10 @@ export type Query = {
|
|||
statss: StatsPage;
|
||||
};
|
||||
|
||||
|
||||
export type QueryPositionsArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryPositionssArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
|
|
@ -54,12 +52,10 @@ export type QueryPositionssArgs = {
|
|||
where?: InputMaybe<PositionsFilter>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryStatsArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryStatssArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ import StakeForgeOutput from '../../onchain/out/Stake.sol/Stake.json' assert { t
|
|||
/**
|
||||
* Kraiken ERC20 token contract ABI
|
||||
*/
|
||||
export const KraikenAbi = KraikenForgeOutput.abi;
|
||||
export const KRAIKEN_ABI = KraikenForgeOutput.abi;
|
||||
|
||||
/**
|
||||
* Stake (Harberger tax staking) contract ABI
|
||||
*/
|
||||
export const StakeAbi = StakeForgeOutput.abi;
|
||||
export const STAKE_ABI = StakeForgeOutput.abi;
|
||||
|
||||
// Re-export for convenience
|
||||
export const ABIS = {
|
||||
Kraiken: KraikenAbi,
|
||||
Stake: StakeAbi,
|
||||
Kraiken: KRAIKEN_ABI,
|
||||
Stake: STAKE_ABI,
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export * from "./staking.js";
|
||||
export * from "./snatch.js";
|
||||
export * from "./ids.js";
|
||||
export * from "./taxRates.js";
|
||||
export * from './staking.js';
|
||||
export * from './snatch.js';
|
||||
export * from './ids.js';
|
||||
export * from './taxRates.js';
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
import { bytesToUint256LittleEndian } from "./subgraph.js";
|
||||
import { bytesToUint256LittleEndian } from './subgraph.js';
|
||||
|
||||
export function toBigIntId(id: string | Uint8Array | number | bigint): bigint {
|
||||
if (typeof id === "bigint") return id;
|
||||
if (typeof id === "number") return BigInt(id);
|
||||
if (typeof id === "string") {
|
||||
const trimmed = id.startsWith("0x") ? id : `0x${id}`;
|
||||
if (typeof id === 'bigint') return id;
|
||||
if (typeof id === 'number') return BigInt(id);
|
||||
if (typeof id === 'string') {
|
||||
const trimmed = id.startsWith('0x') ? id : `0x${id}`;
|
||||
return BigInt(trimmed);
|
||||
}
|
||||
if (id instanceof Uint8Array) {
|
||||
return bytesToUint256LittleEndian(id);
|
||||
}
|
||||
throw new Error("Unsupported position id type");
|
||||
throw new Error('Unsupported position id type');
|
||||
}
|
||||
|
||||
export function decodePositionId(
|
||||
id: string | Uint8Array | number | bigint
|
||||
): bigint {
|
||||
export function decodePositionId(id: string | Uint8Array | number | bigint): bigint {
|
||||
return toBigIntId(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,11 @@
|
|||
export {
|
||||
bytesToUint256LittleEndian,
|
||||
uint256ToBytesLittleEndian,
|
||||
} from "./subgraph.js";
|
||||
export { bytesToUint256LittleEndian, uint256ToBytesLittleEndian } from './subgraph.js';
|
||||
|
||||
// Backward compatible aliases
|
||||
export {
|
||||
bytesToUint256LittleEndian as bytesToUint256,
|
||||
uint256ToBytesLittleEndian as uint256ToBytes,
|
||||
} from "./subgraph.js";
|
||||
export { bytesToUint256LittleEndian as bytesToUint256, uint256ToBytesLittleEndian as uint256ToBytes } from './subgraph.js';
|
||||
|
||||
export { TAX_RATE_OPTIONS, type TaxRateOption } from "./taxRates.js";
|
||||
export { TAX_RATE_OPTIONS, type TaxRateOption } from './taxRates.js';
|
||||
|
||||
export {
|
||||
calculateSnatchShortfall,
|
||||
isPositionDelinquent,
|
||||
} from "./staking.js";
|
||||
export { calculateSnatchShortfall, isPositionDelinquent } from './staking.js';
|
||||
|
||||
export {
|
||||
minimumTaxRate,
|
||||
|
|
@ -23,8 +14,11 @@ export {
|
|||
type SnatchablePosition,
|
||||
type SnatchSelectionOptions,
|
||||
type SnatchSelectionResult,
|
||||
} from "./snatch.js";
|
||||
} from './snatch.js';
|
||||
|
||||
export { decodePositionId } from "./ids.js";
|
||||
export { decodePositionId } from './ids.js';
|
||||
|
||||
export { KraikenAbi, StakeAbi, ABIS } from "./abis.js";
|
||||
export { KRAIKEN_ABI, STAKE_ABI, ABIS } from './abis.js';
|
||||
|
||||
// Backward compatible aliases
|
||||
export { KRAIKEN_ABI as KraikenAbi, STAKE_ABI as StakeAbi } from './abis.js';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Positions } from "./__generated__/graphql.js";
|
||||
import { toBigIntId } from "./ids.js";
|
||||
import type { Positions } from './__generated__/graphql.js';
|
||||
import { toBigIntId } from './ids.js';
|
||||
|
||||
export interface SnatchablePosition {
|
||||
id: bigint;
|
||||
|
|
@ -27,30 +27,16 @@ export interface SnatchSelectionResult {
|
|||
const SHARE_SCALE = 1_000_000n;
|
||||
|
||||
function normaliseAddress(value?: string | null): string {
|
||||
return (value ?? "").toLowerCase();
|
||||
return (value ?? '').toLowerCase();
|
||||
}
|
||||
|
||||
export function minimumTaxRate<T extends { taxRate: number }>(
|
||||
positions: T[],
|
||||
fallback: number = 0
|
||||
): number {
|
||||
export function minimumTaxRate<T extends { taxRate: number }>(positions: T[], fallback: number = 0): number {
|
||||
if (!positions.length) return fallback;
|
||||
return positions.reduce(
|
||||
(min, position) => (position.taxRate < min ? position.taxRate : min),
|
||||
Number.POSITIVE_INFINITY
|
||||
);
|
||||
return positions.reduce((min, position) => (position.taxRate < min ? position.taxRate : min), Number.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
export function selectSnatchPositions(
|
||||
candidates: SnatchablePosition[],
|
||||
options: SnatchSelectionOptions
|
||||
): SnatchSelectionResult {
|
||||
const {
|
||||
shortfallShares,
|
||||
maxTaxRate,
|
||||
includeOwned = false,
|
||||
recipientAddress = null,
|
||||
} = options;
|
||||
export function selectSnatchPositions(candidates: SnatchablePosition[], options: SnatchSelectionOptions): SnatchSelectionResult {
|
||||
const { shortfallShares, maxTaxRate, includeOwned = false, recipientAddress = null } = options;
|
||||
|
||||
if (shortfallShares <= 0n) {
|
||||
return {
|
||||
|
|
@ -62,7 +48,7 @@ export function selectSnatchPositions(
|
|||
|
||||
const recipientNormalised = normaliseAddress(recipientAddress);
|
||||
|
||||
const filtered = candidates.filter((candidate) => {
|
||||
const filtered = candidates.filter(candidate => {
|
||||
if (candidate.taxRate >= maxTaxRate) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -100,17 +86,14 @@ export function selectSnatchPositions(
|
|||
covered = shortfallShares - remaining;
|
||||
}
|
||||
|
||||
if (
|
||||
maxSelectedTaxRate === undefined ||
|
||||
candidate.taxRate > maxSelectedTaxRate
|
||||
) {
|
||||
if (maxSelectedTaxRate === undefined || candidate.taxRate > maxSelectedTaxRate) {
|
||||
maxSelectedTaxRate = candidate.taxRate;
|
||||
if (typeof candidate.taxRateIndex === "number") {
|
||||
if (typeof candidate.taxRateIndex === 'number') {
|
||||
maxSelectedTaxRateIndex = candidate.taxRateIndex;
|
||||
}
|
||||
} else if (
|
||||
candidate.taxRate === maxSelectedTaxRate &&
|
||||
typeof candidate.taxRateIndex === "number" &&
|
||||
typeof candidate.taxRateIndex === 'number' &&
|
||||
candidate.taxRateIndex > (maxSelectedTaxRateIndex ?? -1)
|
||||
) {
|
||||
maxSelectedTaxRateIndex = candidate.taxRateIndex;
|
||||
|
|
@ -133,14 +116,12 @@ export function getSnatchList(
|
|||
stakeTotalSupply: bigint
|
||||
): bigint[] {
|
||||
if (stakeTotalSupply <= 0n) {
|
||||
throw new Error("stakeTotalSupply must be greater than zero");
|
||||
throw new Error('stakeTotalSupply must be greater than zero');
|
||||
}
|
||||
|
||||
const candidates: SnatchablePosition[] = positions.map((position) => {
|
||||
const candidates: SnatchablePosition[] = positions.map(position => {
|
||||
const shareRatio = Number(position.share);
|
||||
const scaledShares = BigInt(
|
||||
Math.round(shareRatio * Number(SHARE_SCALE))
|
||||
);
|
||||
const scaledShares = BigInt(Math.round(shareRatio * Number(SHARE_SCALE)));
|
||||
|
||||
return {
|
||||
id: toBigIntId(position.id),
|
||||
|
|
@ -157,8 +138,8 @@ export function getSnatchList(
|
|||
});
|
||||
|
||||
if (result.remainingShortfall > 0n) {
|
||||
throw new Error("Not enough capacity");
|
||||
throw new Error('Not enough capacity');
|
||||
}
|
||||
|
||||
return result.selected.map((candidate) => candidate.id);
|
||||
return result.selected.map(candidate => candidate.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function calculateSnatchShortfall(
|
|||
capDenominator: bigint = 10n
|
||||
): bigint {
|
||||
if (capDenominator === 0n) {
|
||||
throw new Error("capDenominator must be greater than zero");
|
||||
throw new Error('capDenominator must be greater than zero');
|
||||
}
|
||||
|
||||
const cap = (stakeTotalSupply * capNumerator) / capDenominator;
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
|
||||
export function bytesToUint256LittleEndian(bytes: Uint8Array): bigint {
|
||||
// console.log(hexString);
|
||||
// const cleanHexString = hexString.startsWith('0x') ? hexString.substring(2) : hexString;
|
||||
// const bytes = new Uint8Array(Math.ceil(cleanHexString.length / 2));
|
||||
// for (let i = 0, j = 0; i < cleanHexString.length; i += 2, j++) {
|
||||
// bytes[j] = parseInt(cleanHexString.slice(i, i + 2), 16);
|
||||
// }
|
||||
let value: bigint = 0n;
|
||||
// console.log(hexString);
|
||||
// const cleanHexString = hexString.startsWith('0x') ? hexString.substring(2) : hexString;
|
||||
// const bytes = new Uint8Array(Math.ceil(cleanHexString.length / 2));
|
||||
// for (let i = 0, j = 0; i < cleanHexString.length; i += 2, j++) {
|
||||
// bytes[j] = parseInt(cleanHexString.slice(i, i + 2), 16);
|
||||
// }
|
||||
let value: bigint = 0n;
|
||||
|
||||
for (let i = bytes.length - 1; i >= 0; i--) {
|
||||
value = (value << 8n) | BigInt(bytes[i]);
|
||||
}
|
||||
for (let i = bytes.length - 1; i >= 0; i--) {
|
||||
value = (value << 8n) | BigInt(bytes[i]);
|
||||
}
|
||||
|
||||
return value;
|
||||
return value;
|
||||
}
|
||||
|
||||
export function uint256ToBytesLittleEndian(value: bigint): Uint8Array {
|
||||
const bytes = new Uint8Array(4);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
bytes[i] = Number((value >> (8n * BigInt(i))) & 0xFFn);
|
||||
}
|
||||
return bytes;
|
||||
// let hexString = '0x';
|
||||
const bytes = new Uint8Array(4);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
bytes[i] = Number((value >> (8n * BigInt(i))) & 0xffn);
|
||||
}
|
||||
return bytes;
|
||||
// let hexString = '0x';
|
||||
|
||||
// for (let i = 0; i < bytes.length; i++) {
|
||||
// // Convert each byte to a hexadecimal string and pad with zero if needed
|
||||
// hexString += bytes[i].toString(16).padStart(2, '0');
|
||||
// }
|
||||
// for (let i = 0; i < bytes.length; i++) {
|
||||
// // Convert each byte to a hexadecimal string and pad with zero if needed
|
||||
// hexString += bytes[i].toString(16).padStart(2, '0');
|
||||
// }
|
||||
|
||||
// return hexString;
|
||||
}
|
||||
// return hexString;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { describe, expect, test } from "@jest/globals";
|
||||
import { bytesToUint256LittleEndian, uint256ToBytesLittleEndian } from "../subgraph";
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { bytesToUint256LittleEndian, uint256ToBytesLittleEndian } from '../subgraph';
|
||||
import { Position, PositionStatus } from '../__generated__/graphql';
|
||||
|
||||
|
||||
describe('BigInt Conversion Functions', () => {
|
||||
test('converts uint256 to bytes and back (little endian)', async () => {
|
||||
|
||||
const mockPos: Position = {
|
||||
__typename: 'Position',
|
||||
id: uint256ToBytesLittleEndian(3n),
|
||||
|
|
@ -23,8 +21,7 @@ describe('BigInt Conversion Functions', () => {
|
|||
}
|
||||
expect(hexString).toEqual('0x03000000');
|
||||
|
||||
// return hexString;
|
||||
// return hexString;
|
||||
expect(bytesToUint256LittleEndian(mockPos.id)).toEqual(3n);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { describe, expect, test } from "@jest/globals";
|
||||
import { decodePositionId } from "../ids";
|
||||
import { uint256ToBytesLittleEndian } from "../subgraph";
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { decodePositionId } from '../ids';
|
||||
import { uint256ToBytesLittleEndian } from '../subgraph';
|
||||
|
||||
describe("ids", () => {
|
||||
test("decodePositionId works across representations", () => {
|
||||
describe('ids', () => {
|
||||
test('decodePositionId works across representations', () => {
|
||||
const id = 12345n;
|
||||
const hex = `0x${id.toString(16)}`;
|
||||
const bytes = uint256ToBytesLittleEndian(id);
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
import { describe, expect, test } from "@jest/globals";
|
||||
import {
|
||||
getSnatchList,
|
||||
minimumTaxRate,
|
||||
selectSnatchPositions,
|
||||
type SnatchablePosition,
|
||||
} from "../snatch";
|
||||
import { uint256ToBytesLittleEndian } from "../subgraph";
|
||||
import type { Position } from "../__generated__/graphql";
|
||||
import { PositionStatus } from "../__generated__/graphql";
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { getSnatchList, minimumTaxRate, selectSnatchPositions, type SnatchablePosition } from '../snatch';
|
||||
import { uint256ToBytesLittleEndian } from '../subgraph';
|
||||
import type { Position } from '../__generated__/graphql';
|
||||
import { PositionStatus } from '../__generated__/graphql';
|
||||
|
||||
describe("snatch", () => {
|
||||
test("minimumTaxRate finds the lowest tax", () => {
|
||||
describe('snatch', () => {
|
||||
test('minimumTaxRate finds the lowest tax', () => {
|
||||
const rates = [{ taxRate: 0.12 }, { taxRate: 0.05 }, { taxRate: 0.08 }];
|
||||
expect(minimumTaxRate(rates, 1)).toBeCloseTo(0.05);
|
||||
expect(minimumTaxRate([], 0.42)).toBe(0.42);
|
||||
});
|
||||
|
||||
test("selectSnatchPositions chooses cheapest positions first", () => {
|
||||
test('selectSnatchPositions chooses cheapest positions first', () => {
|
||||
const candidates: SnatchablePosition[] = [
|
||||
{ id: 1n, stakeShares: 30n, taxRate: 0.05, taxRateIndex: 5 },
|
||||
{ id: 2n, stakeShares: 40n, taxRate: 0.03, taxRateIndex: 3 },
|
||||
|
|
@ -28,13 +23,13 @@ describe("snatch", () => {
|
|||
maxTaxRate: 0.08,
|
||||
});
|
||||
|
||||
expect(result.selected.map((item) => item.id)).toEqual([2n, 1n]);
|
||||
expect(result.selected.map(item => item.id)).toEqual([2n, 1n]);
|
||||
expect(result.remainingShortfall).toBe(0n);
|
||||
expect(result.coveredShares).toBe(60n);
|
||||
expect(result.maxSelectedTaxRateIndex).toBe(5);
|
||||
});
|
||||
|
||||
test("selectSnatchPositions keeps track of remaining shortfall", () => {
|
||||
test('selectSnatchPositions keeps track of remaining shortfall', () => {
|
||||
const candidates: SnatchablePosition[] = [
|
||||
{ id: 1n, stakeShares: 10n, taxRate: 0.01 },
|
||||
{ id: 2n, stakeShares: 10n, taxRate: 0.02 },
|
||||
|
|
@ -48,14 +43,14 @@ describe("snatch", () => {
|
|||
expect(result.remainingShortfall).toBe(30n);
|
||||
});
|
||||
|
||||
test("getSnatchList converts subgraph positions", () => {
|
||||
test('getSnatchList converts subgraph positions', () => {
|
||||
const stakeTotalSupply = 1_000_000n * 10n ** 18n;
|
||||
|
||||
const positions: Position[] = [
|
||||
{
|
||||
__typename: "Position",
|
||||
__typename: 'Position',
|
||||
id: uint256ToBytesLittleEndian(1n),
|
||||
owner: "0xowner1",
|
||||
owner: '0xowner1',
|
||||
share: 0.0001,
|
||||
creationTime: 0,
|
||||
lastTaxTime: 0,
|
||||
|
|
@ -63,9 +58,9 @@ describe("snatch", () => {
|
|||
status: PositionStatus.Active,
|
||||
},
|
||||
{
|
||||
__typename: "Position",
|
||||
__typename: 'Position',
|
||||
id: uint256ToBytesLittleEndian(2n),
|
||||
owner: "0xowner2",
|
||||
owner: '0xowner2',
|
||||
share: 0.0002,
|
||||
creationTime: 0,
|
||||
lastTaxTime: 0,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { describe, expect, test } from "@jest/globals";
|
||||
import { calculateSnatchShortfall, isPositionDelinquent } from "../staking";
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { calculateSnatchShortfall, isPositionDelinquent } from '../staking';
|
||||
|
||||
describe("staking", () => {
|
||||
test("calculateSnatchShortfall returns zero when within cap", () => {
|
||||
describe('staking', () => {
|
||||
test('calculateSnatchShortfall returns zero when within cap', () => {
|
||||
const outstanding = 100n;
|
||||
const desired = 50n;
|
||||
const total = 1000n;
|
||||
|
|
@ -11,7 +11,7 @@ describe("staking", () => {
|
|||
expect(result).toBe(0n);
|
||||
});
|
||||
|
||||
test("calculateSnatchShortfall returns positive remainder when exceeding cap", () => {
|
||||
test('calculateSnatchShortfall returns positive remainder when exceeding cap', () => {
|
||||
const outstanding = 200n;
|
||||
const desired = 200n;
|
||||
const total = 1000n;
|
||||
|
|
@ -20,7 +20,7 @@ describe("staking", () => {
|
|||
expect(result).toBe(200n);
|
||||
});
|
||||
|
||||
test("isPositionDelinquent respects tax rate windows", () => {
|
||||
test('isPositionDelinquent respects tax rate windows', () => {
|
||||
const now = 1_000_000;
|
||||
const taxRate = 0.5; // 50%
|
||||
const windowSeconds = (365 * 24 * 60 * 60) / taxRate;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { describe, expect, test } from "@jest/globals";
|
||||
import { TAX_RATE_OPTIONS } from "../taxRates";
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { TAX_RATE_OPTIONS } from '../taxRates';
|
||||
|
||||
describe("taxRates", () => {
|
||||
test("tax rate options exported for consumers", () => {
|
||||
describe('taxRates', () => {
|
||||
test('tax rate options exported for consumers', () => {
|
||||
expect(TAX_RATE_OPTIONS.length).toBeGreaterThan(0);
|
||||
expect(TAX_RATE_OPTIONS[0]).toEqual(
|
||||
expect.objectContaining({ index: 0, year: 1, decimal: 0.01 })
|
||||
);
|
||||
expect(TAX_RATE_OPTIONS[0]).toEqual(expect.objectContaining({ index: 0, year: 1, decimal: 0.01 }));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue