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 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
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
34 changes: 26 additions & 8 deletions ensawards.org/data/awards/utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
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.
}

if (a.price.amount > b.price.amount) return -1;
if (a.price.amount < b.price.amount) return 1;

return a.awardedAt - b.awardedAt;
};
Expand All @@ -19,6 +30,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