Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions src/__tests/encoders/batchCreatePodListing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { TokenValue } from "@/classes/TokenValue";
import { diamondABI } from "@/constants/abi/diamondABI";
import batchCreatePodListing, { CreatePodListingParams } from "@/encoders/batchCreatePodListing";
import { FarmToMode } from "@/utils/types";
import { decodeFunctionData } from "viem";
import { describe, expect, it } from "vitest";

const mockAddress = "0x1234567890abcdef1234567890abcdef12345678" as const;

interface DecodedPodListing {
lister: string;
fieldId: bigint;
index: bigint;
start: bigint;
podAmount: bigint;
pricePerPod: number;
maxHarvestableIndex: bigint;
minFillAmount: bigint;
mode: number;
}

function decodeResult(data: `0x${string}`) {
const decoded = decodeFunctionData({
abi: diamondABI,
data,
});
return {
functionName: decoded.functionName,
listings: decoded.args[0] as unknown as DecodedPodListing[],
};
}

function createMockListing(overrides: Partial<CreatePodListingParams> = {}): CreatePodListingParams {
return {
lister: mockAddress,
fieldId: 0n,
index: TokenValue.fromBlockchain("1000000", 6),
start: TokenValue.fromBlockchain("0", 6),
podAmount: TokenValue.fromBlockchain("5000000", 6),
pricePerPod: TokenValue.fromBlockchain("500000", 6),
maxHarvestableIndex: TokenValue.fromBlockchain("100000000000", 6),
minFillAmount: TokenValue.fromBlockchain("1000000", 6),
mode: FarmToMode.EXTERNAL,
...overrides,
};
}

describe("batchCreatePodListing encoder", () => {
it("encodes a single listing", () => {
const listing = createMockListing();
const result = batchCreatePodListing([listing]);

expect(result).toMatch(/^0x/);
expect(result.length).toBeGreaterThan(10);

const { functionName, listings } = decodeResult(result);

expect(functionName).toBe("batchCreatePodListing");
expect(listings).toHaveLength(1);

const decodedListing = listings[0];
expect(decodedListing.lister.toLowerCase()).toBe(mockAddress.toLowerCase());
expect(decodedListing.fieldId).toBe(0n);
expect(decodedListing.index).toBe(listing.index.toBigInt());
expect(decodedListing.start).toBe(listing.start.toBigInt());
expect(decodedListing.podAmount).toBe(listing.podAmount.toBigInt());
expect(decodedListing.pricePerPod).toBe(Number(listing.pricePerPod.toBigInt()));
expect(decodedListing.maxHarvestableIndex).toBe(listing.maxHarvestableIndex.toBigInt());
expect(decodedListing.minFillAmount).toBe(listing.minFillAmount.toBigInt());
expect(decodedListing.mode).toBe(Number(FarmToMode.EXTERNAL));
});

it("encodes multiple listings", () => {
const listings = [
createMockListing({ index: TokenValue.fromBlockchain("1000000", 6) }),
createMockListing({ index: TokenValue.fromBlockchain("2000000", 6) }),
createMockListing({ index: TokenValue.fromBlockchain("3000000", 6) }),
];

const result = batchCreatePodListing(listings);
const { listings: decoded } = decodeResult(result);

expect(decoded).toHaveLength(3);
expect(decoded[0].index).toBe(listings[0].index.toBigInt());
expect(decoded[1].index).toBe(listings[1].index.toBigInt());
expect(decoded[2].index).toBe(listings[2].index.toBigInt());
});

it("converts pricePerPod to number (uint24)", () => {
const listing = createMockListing({
pricePerPod: TokenValue.fromBlockchain("800000", 6),
});

const result = batchCreatePodListing([listing]);
const { listings } = decodeResult(result);

expect(typeof listings[0].pricePerPod).toBe("number");
expect(listings[0].pricePerPod).toBe(800000);
});

it("handles FarmToMode.INTERNAL", () => {
const listing = createMockListing({ mode: FarmToMode.INTERNAL });

const result = batchCreatePodListing([listing]);
const { listings } = decodeResult(result);

expect(listings[0].mode).toBe(Number(FarmToMode.INTERNAL));
});

it("converts TokenValue fields to BigInt", () => {
const listing = createMockListing({
index: TokenValue.fromHuman("100", 6),
start: TokenValue.fromHuman("10", 6),
podAmount: TokenValue.fromHuman("50", 6),
maxHarvestableIndex: TokenValue.fromHuman("999999", 6),
minFillAmount: TokenValue.fromHuman("1", 6),
});

const result = batchCreatePodListing([listing]);
const { listings } = decodeResult(result);

const d = listings[0];
expect(d.index).toBe(100000000n);
expect(d.start).toBe(10000000n);
expect(d.podAmount).toBe(50000000n);
expect(d.maxHarvestableIndex).toBe(999999000000n);
expect(d.minFillAmount).toBe(1000000n);
});

it("preserves lister address", () => {
const customAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as const;
const listing = createMockListing({ lister: customAddress });

const result = batchCreatePodListing([listing]);
const { listings } = decodeResult(result);

expect(listings[0].lister.toLowerCase()).toBe(customAddress.toLowerCase());
});
});
225 changes: 225 additions & 0 deletions src/__tests/encoders/batchFillPodListing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import { TokenValue } from "@/classes/TokenValue";
import { diamondABI } from "@/constants/abi/diamondABI";
import batchFillPodListing, { FillPodListingParams } from "@/encoders/batchFillPodListing";
import { FarmFromMode, FarmToMode } from "@/utils/types";
import { decodeFunctionData } from "viem";
import { describe, expect, it } from "vitest";

const mockAddress = "0x1234567890abcdef1234567890abcdef12345678" as const;

interface DecodedFillParams {
podListing: {
lister: string;
fieldId: bigint;
index: bigint;
start: bigint;
podAmount: bigint;
pricePerPod: number;
maxHarvestableIndex: bigint;
minFillAmount: bigint;
mode: number;
};
beanAmount: bigint;
mode: number;
}

function decodeResult(data: `0x${string}`) {
const decoded = decodeFunctionData({
abi: diamondABI,
data,
});
return {
functionName: decoded.functionName,
params: decoded.args[0] as unknown as DecodedFillParams[],
};
}

function createMockFillParams(overrides: Partial<FillPodListingParams> = {}): FillPodListingParams {
return {
podListing: {
lister: mockAddress,
fieldId: 0n,
index: TokenValue.fromBlockchain("1000000", 6),
start: TokenValue.fromBlockchain("0", 6),
podAmount: TokenValue.fromBlockchain("5000000", 6),
pricePerPod: TokenValue.fromBlockchain("500000", 6),
maxHarvestableIndex: TokenValue.fromBlockchain("100000000000", 6),
minFillAmount: TokenValue.fromBlockchain("1000000", 6),
mode: FarmToMode.EXTERNAL,
...overrides?.podListing,
},
beanAmount: TokenValue.fromBlockchain("2000000", 6),
mode: FarmFromMode.EXTERNAL,
...("beanAmount" in overrides ? { beanAmount: overrides.beanAmount } : {}),
...("mode" in overrides && overrides.mode !== undefined ? { mode: overrides.mode } : {}),
};
}

describe("batchFillPodListing encoder", () => {
it("encodes a single fill", () => {
const fill = createMockFillParams();
const result = batchFillPodListing([fill]);

expect(result).toMatch(/^0x/);
expect(result.length).toBeGreaterThan(10);

const { functionName, params } = decodeResult(result);

expect(functionName).toBe("batchFillPodListing");
expect(params).toHaveLength(1);

const decoded = params[0];
expect(decoded.podListing.lister.toLowerCase()).toBe(mockAddress.toLowerCase());
expect(decoded.podListing.fieldId).toBe(0n);
expect(decoded.podListing.index).toBe(fill.podListing.index.toBigInt());
expect(decoded.podListing.start).toBe(fill.podListing.start.toBigInt());
expect(decoded.podListing.podAmount).toBe(fill.podListing.podAmount.toBigInt());
expect(decoded.podListing.pricePerPod).toBe(Number(fill.podListing.pricePerPod.toBigInt()));
expect(decoded.podListing.maxHarvestableIndex).toBe(fill.podListing.maxHarvestableIndex.toBigInt());
expect(decoded.podListing.minFillAmount).toBe(fill.podListing.minFillAmount.toBigInt());
expect(decoded.podListing.mode).toBe(Number(FarmToMode.EXTERNAL));
expect(decoded.beanAmount).toBe(fill.beanAmount.toBigInt());
expect(decoded.mode).toBe(Number(FarmFromMode.EXTERNAL));
});

it("encodes multiple fills", () => {
const fills: FillPodListingParams[] = [
createMockFillParams(),
createMockFillParams({
podListing: {
lister: mockAddress,
fieldId: 0n,
index: TokenValue.fromBlockchain("2000000", 6),
start: TokenValue.fromBlockchain("0", 6),
podAmount: TokenValue.fromBlockchain("3000000", 6),
pricePerPod: TokenValue.fromBlockchain("600000", 6),
maxHarvestableIndex: TokenValue.fromBlockchain("100000000000", 6),
minFillAmount: TokenValue.fromBlockchain("500000", 6),
mode: FarmToMode.EXTERNAL,
},
beanAmount: TokenValue.fromBlockchain("1500000", 6),
mode: FarmFromMode.INTERNAL,
}),
createMockFillParams({
podListing: {
lister: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" as const,
fieldId: 0n,
index: TokenValue.fromBlockchain("3000000", 6),
start: TokenValue.fromBlockchain("100000", 6),
podAmount: TokenValue.fromBlockchain("8000000", 6),
pricePerPod: TokenValue.fromBlockchain("450000", 6),
maxHarvestableIndex: TokenValue.fromBlockchain("200000000000", 6),
minFillAmount: TokenValue.fromBlockchain("2000000", 6),
mode: FarmToMode.INTERNAL,
},
beanAmount: TokenValue.fromBlockchain("4000000", 6),
mode: FarmFromMode.EXTERNAL,
}),
];

const result = batchFillPodListing(fills);
const { params } = decodeResult(result);

expect(params).toHaveLength(3);
expect(params[0].podListing.index).toBe(fills[0].podListing.index.toBigInt());
expect(params[1].podListing.index).toBe(fills[1].podListing.index.toBigInt());
expect(params[2].podListing.index).toBe(fills[2].podListing.index.toBigInt());
expect(params[1].beanAmount).toBe(fills[1].beanAmount.toBigInt());
expect(params[2].podListing.lister.toLowerCase()).toBe("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd");
});

it("converts pricePerPod to number (uint24)", () => {
const fill = createMockFillParams({
podListing: {
lister: mockAddress,
fieldId: 0n,
index: TokenValue.fromBlockchain("1000000", 6),
start: TokenValue.fromBlockchain("0", 6),
podAmount: TokenValue.fromBlockchain("5000000", 6),
pricePerPod: TokenValue.fromBlockchain("800000", 6),
maxHarvestableIndex: TokenValue.fromBlockchain("100000000000", 6),
minFillAmount: TokenValue.fromBlockchain("1000000", 6),
mode: FarmToMode.EXTERNAL,
},
});

const result = batchFillPodListing([fill]);
const { params } = decodeResult(result);

expect(typeof params[0].podListing.pricePerPod).toBe("number");
expect(params[0].podListing.pricePerPod).toBe(800000);
});

it("handles FarmFromMode.INTERNAL", () => {
const fill = createMockFillParams({
podListing: {
lister: mockAddress,
fieldId: 0n,
index: TokenValue.fromBlockchain("1000000", 6),
start: TokenValue.fromBlockchain("0", 6),
podAmount: TokenValue.fromBlockchain("5000000", 6),
pricePerPod: TokenValue.fromBlockchain("500000", 6),
maxHarvestableIndex: TokenValue.fromBlockchain("100000000000", 6),
minFillAmount: TokenValue.fromBlockchain("1000000", 6),
mode: FarmToMode.EXTERNAL,
},
beanAmount: TokenValue.fromBlockchain("2000000", 6),
mode: FarmFromMode.INTERNAL,
});

const result = batchFillPodListing([fill]);
const { params } = decodeResult(result);

expect(params[0].mode).toBe(Number(FarmFromMode.INTERNAL));
});

it("handles FarmToMode on podListing.mode", () => {
const fill = createMockFillParams({
podListing: {
lister: mockAddress,
fieldId: 0n,
index: TokenValue.fromBlockchain("1000000", 6),
start: TokenValue.fromBlockchain("0", 6),
podAmount: TokenValue.fromBlockchain("5000000", 6),
pricePerPod: TokenValue.fromBlockchain("500000", 6),
maxHarvestableIndex: TokenValue.fromBlockchain("100000000000", 6),
minFillAmount: TokenValue.fromBlockchain("1000000", 6),
mode: FarmToMode.INTERNAL,
},
});

const result = batchFillPodListing([fill]);
const { params } = decodeResult(result);

expect(params[0].podListing.mode).toBe(Number(FarmToMode.INTERNAL));
});

it("converts TokenValue fields to BigInt correctly", () => {
const fill = createMockFillParams({
podListing: {
lister: mockAddress,
fieldId: 0n,
index: TokenValue.fromHuman("100", 6),
start: TokenValue.fromHuman("10", 6),
podAmount: TokenValue.fromHuman("50", 6),
pricePerPod: TokenValue.fromBlockchain("500000", 6),
maxHarvestableIndex: TokenValue.fromHuman("999999", 6),
minFillAmount: TokenValue.fromHuman("1", 6),
mode: FarmToMode.EXTERNAL,
},
beanAmount: TokenValue.fromHuman("25", 6),
mode: FarmFromMode.EXTERNAL,
});

const result = batchFillPodListing([fill]);
const { params } = decodeResult(result);

const d = params[0];
expect(d.podListing.index).toBe(100000000n);
expect(d.podListing.start).toBe(10000000n);
expect(d.podListing.podAmount).toBe(50000000n);
expect(d.podListing.maxHarvestableIndex).toBe(999999000000n);
expect(d.podListing.minFillAmount).toBe(1000000n);
expect(d.beanAmount).toBe(25000000n);
});
});
10 changes: 8 additions & 2 deletions src/components/CombineSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,19 @@ export default function CombineSelect({ setTransferData, token, disabled }: Comb
setSubmitting(true);
toast.loading("Combining deposits...");

const encodedCalls = encodeGroupCombineCalls(validGroups, token, farmerDeposits.deposits);
const encodedCall = encodeGroupCombineCalls(validGroups, token, farmerDeposits.deposits);

if (!encodedCall) {
toast.error("No deposits to combine");
setSubmitting(false);
return;
}

await writeWithEstimateGas({
address: protocolAddress,
abi: diamondABI,
functionName: "farm",
args: [encodedCalls],
args: [[encodedCall]],
});

setOpen(false);
Expand Down
Loading