Skip to content
This repository was archived by the owner on Aug 5, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 7 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
7 changes: 4 additions & 3 deletions ensawards.org/data/awards/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import type { App } from "data/apps/types.ts";
import type { EntityMetadata } from "data/entity-metadata/types.ts";
import type { IncentiveProgram, IncentiveProgramSlug } from "data/incentive-programs/types.ts";
import type { Protocol } from "data/protocols/types.ts";
import { type EnsTokens } from "data/shared/ensTokens.ts";
import type { TransactionRef } from "data/shared/transactionRef.ts";
import type { AccountId, UnixTimestamp } from "enssdk";

import type { Price } from "@ensnode/ensnode-sdk";

export const AwardTypes = {
FinancialAward: "financial-award",
RecognitionAward: "recognition-award",
Expand Down Expand Up @@ -53,11 +54,11 @@ export interface AwardAbstract<AwardTypeT extends AwardType> {
export interface AwardRecognition extends AwardAbstract<typeof AwardTypes.RecognitionAward> {}

export interface AwardFinancial extends AwardAbstract<typeof AwardTypes.FinancialAward> {
/** Amount of the award in $ENS.
/** Financial award in any currency supported by {@link Price}.
*
* @invariant Award amount must be finite and greater than 0.
Comment thread
Y3drk marked this conversation as resolved.
*/
price: EnsTokens; // should be replaced with `Price` from enssdk when Issue#1941 is completed.
price: Price;

/**
* Details of the transaction associated with the distribution of this award.
Expand Down
86 changes: 73 additions & 13 deletions ensawards.org/data/awards/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,72 @@ import type { ChainId } from "enssdk";
import { zeroAddress, zeroHash } from "viem";
import { describe, expect, it } from "vitest";

import { CurrencyIds, parseEnsTokens, parseUsdc } from "@ensnode/ensnode-sdk";

import { type AwardFinancial, AwardTypes } from "./types.ts";
import { sortFinancialAwardsByPrice } from "./utils.ts";
import { isValidAwardValue, sortFinancialAwardsByPrice } from "./utils.ts";

describe("awards utils", () => {
describe("isValidAwardValue", () => {
it("should return true for valid award values", () => {
expect(
isValidAwardValue(parseUsdc("1000")),
"Should return true for positive finite amounts",
).toBe(true);
});

it("should return false for invalid award values", () => {
expect(isValidAwardValue(parseUsdc("0")), "Should return false for zero amount").toBe(false);

// Not possible to have negative amounts with parseX function (it will throw instead),
// but we want to ensure the validation function
// behaves correctly if it receives such input
expect(
isValidAwardValue({ currency: CurrencyIds.USDC, amount: -10n }),
"Should return false for negative amount",
).toBe(false);
});
});

describe("sortFinancialAwardsByPrice", () => {
const placeholderChainId: ChainId = 1;

it("Should throw an error if awards have different currencies", () => {
const awardA: AwardFinancial = {
associatedIncentiveProgramSlug: "placeholder-incentive-program",
type: AwardTypes.FinancialAward,
awardedTo: {
chainId: placeholderChainId,
address: zeroAddress,
},
price: parseUsdc("100"),
awardedAt: 1,
transaction: {
chainId: placeholderChainId,
transactionHash: zeroHash,
},
};

const awardB: AwardFinancial = {
associatedIncentiveProgramSlug: "placeholder-incentive-program",
type: AwardTypes.FinancialAward,
awardedTo: {
chainId: placeholderChainId,
address: zeroAddress,
},
price: parseEnsTokens("100"),
awardedAt: 1,
transaction: {
chainId: placeholderChainId,
transactionHash: zeroHash,
},
};

expect(() => sortFinancialAwardsByPrice(awardA, awardB)).toThrow(
"Cannot compare awards with `price` in different currencies: USDC vs ENSTokens",
);
});

it("should sort awards by award value in descending order", () => {
const awards: AwardFinancial[] = [
{
Expand All @@ -17,7 +77,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 1,
transaction: {
chainId: placeholderChainId,
Expand All @@ -31,7 +91,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 200,
price: parseEnsTokens("200"),
awardedAt: 2,
transaction: {
chainId: placeholderChainId,
Expand All @@ -45,7 +105,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 150,
price: parseEnsTokens("150"),
awardedAt: 0,
transaction: {
chainId: placeholderChainId,
Expand All @@ -62,7 +122,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 200,
price: parseEnsTokens("200"),
awardedAt: 2,
transaction: {
chainId: placeholderChainId,
Expand All @@ -76,7 +136,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 150,
price: parseEnsTokens("150"),
awardedAt: 0,
transaction: {
chainId: placeholderChainId,
Expand All @@ -90,7 +150,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 1,
transaction: {
chainId: placeholderChainId,
Expand All @@ -114,7 +174,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 2,
transaction: {
chainId: placeholderChainId,
Expand All @@ -128,7 +188,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 1,
transaction: {
chainId: placeholderChainId,
Expand All @@ -142,7 +202,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 3,
transaction: {
chainId: placeholderChainId,
Expand All @@ -159,7 +219,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 1,
transaction: {
chainId: placeholderChainId,
Expand All @@ -173,7 +233,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 2,
transaction: {
chainId: placeholderChainId,
Expand All @@ -187,7 +247,7 @@ describe("awards utils", () => {
chainId: placeholderChainId,
address: zeroAddress,
},
price: 100,
price: parseEnsTokens("100"),
awardedAt: 3,
transaction: {
chainId: placeholderChainId,
Expand Down
37 changes: 29 additions & 8 deletions ensawards.org/data/awards/utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import { type EnsTokens } from "data/shared/ensTokens";
import { interpretCurrency } from "data/shared/currencies";

import { type Award, type AwardFinancial } from "./types";
import type { Price } from "@ensnode/ensnode-sdk";

import { type AwardFinancial } from "./types";

/**
* Sorts {@link Award}s of {@link AwardFinancial} type.
* Sorts {@link AwardFinancial}s.
*
* Prioritizes awards with higher {@link AwardFinancial.price} and,
* Prioritizes awards with higher {@link AwardFinancial.price.amount} and,
* in case of a tie, earlier {@link AwardFinancial.awardedAt} date.
*
* @throws
* If the two awards have different {@link AwardFinancial.price.currency} values.
*/
export const sortFinancialAwardsByPrice = (a: AwardFinancial, b: AwardFinancial): number => {
if (a.price > b.price) return -1;
if (a.price < b.price) return 1;
if (a.price.currency !== b.price.currency) {
throw new Error(
`Cannot compare awards with \`price\` in different currencies: ${a.price.currency} vs ${b.price.currency}`,
);
Comment thread
Y3drk marked this conversation as resolved.
Comment thread
Y3drk marked this conversation as resolved.
}

const aPriceAmount = a.price.amount;
const bPriceAmount = b.price.amount;

if (aPriceAmount > bPriceAmount) return -1;
if (aPriceAmount < bPriceAmount) return 1;
Comment thread
Y3drk marked this conversation as resolved.
Outdated

return a.awardedAt - b.awardedAt;
};
Expand All @@ -19,6 +33,13 @@ export const sortFinancialAwardsByPrice = (a: AwardFinancial, b: AwardFinancial)
* Checks if a given award value is valid
* according to the invariants defined in {@link AwardFinancial}.
*/
export const isValidAwardValue = (awardValue: EnsTokens): boolean => {
return Number.isFinite(awardValue) && awardValue > 0;
export const isValidAwardValue = (awardPrice: Price): boolean => {
// Check the raw `Price.amount` value first
if (awardPrice.amount <= 0n) {
return false;
}

// Then check the user-facing interpreted value
const interpretedAmount = interpretCurrency(awardPrice);
return Number.isFinite(interpretedAmount) && interpretedAmount > 0;
};
Comment thread
Y3drk marked this conversation as resolved.
Comment thread
Y3drk marked this conversation as resolved.
Loading
Loading