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
5 changes: 5 additions & 0 deletions .changeset/modern-groups-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ensnode-sdk": patch
---
Comment thread
tk-o marked this conversation as resolved.

Aligned `isRegistrationFullyExpired` and `isRegistrationInGracePeriod` helpers with onchain logic.
Comment thread
tk-o marked this conversation as resolved.
Comment thread
tk-o marked this conversation as resolved.
5 changes: 5 additions & 0 deletions .changeset/two-corners-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": patch
---

Updated ENSv2Registry handling for non-expiring reverse-name registrations.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type AccountId,
asLiteralLabel,
isNormalizedAddress,
type LabelHash,
labelhashLiteralLabel,
makeENSv2DomainId,
Expand Down Expand Up @@ -103,10 +104,21 @@ export default function () {
`Invariant(ENSv2Registry:Label[Registered|Reserved]): Existing unexpired Registration found, expected none or expired.\n${toJson(registration, { pretty: true })}`,
);
}
} else {
// Invariant: if this is a Registration, unless it is a Reservation, it should be fully expired
} else if (registration) {
// There's an existing Registration record, so we need to handle it carefully.
// Registrations for reverse names are a special case, since they can
// never expire. Therefore, we need to skip the expiration check for
// reverse name registrations and allow a new Registration record to be
// created for the same label.
// For a reverse name registration, the registrant ID is the label with `0x` prefix.
const maybeReverseNameLabel = `0x${label}`;
const isReverseNameRegistration =
isNormalizedAddress(maybeReverseNameLabel) &&
Comment thread
tk-o marked this conversation as resolved.
registration.registrantId === maybeReverseNameLabel;

// Invariant: if this is a Registration, unless it is a Reservation or a reverse name Registration, it should be fully expired
if (
registration &&
!isReverseNameRegistration &&
registration.type !== "ENSv2RegistryReservation" &&
!isRegistrationFullyExpired(registration, event.block.timestamp)
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { describe, expect, it } from "vitest";

import {
isRegistrationExpired,
isRegistrationFullyExpired,
isRegistrationInGracePeriod,
} from "./registration-expiration";
Comment thread
tk-o marked this conversation as resolved.

describe("registration expiration", () => {
const expiry = 1000n;
const gracePeriod = 100n;

describe("isRegistrationExpired", () => {
it.each([
{ now: 999n, expected: false, description: "before expiry" },
{ now: 1000n, expected: true, description: "at expiry" },
{ now: 1001n, expected: true, description: "after expiry" },
])("returns $expected when $description", ({ now, expected }) => {
expect(isRegistrationExpired({ expiry, gracePeriod }, now)).toBe(expected);
});

it("returns false when expiry is null", () => {
expect(isRegistrationExpired({ expiry: null, gracePeriod }, 2000n)).toBe(false);
});
});

describe("isRegistrationFullyExpired", () => {
it.each([
{ now: 999n, expected: false, description: "before expiry" },
{ now: 1000n, expected: false, description: "at expiry" },
{ now: 1050n, expected: false, description: "during grace period" },
{ now: 1100n, expected: false, description: "at expiry + grace period" },
{ now: 1101n, expected: true, description: "after expiry + grace period" },
])("returns $expected when $description", ({ now, expected }) => {
expect(isRegistrationFullyExpired({ expiry, gracePeriod }, now)).toBe(expected);
});

it("returns false when expiry is null", () => {
expect(isRegistrationFullyExpired({ expiry: null, gracePeriod }, 2000n)).toBe(false);
});

it("treats null grace period as zero", () => {
expect(isRegistrationFullyExpired({ expiry, gracePeriod: null }, 1000n)).toBe(false);
expect(isRegistrationFullyExpired({ expiry, gracePeriod: null }, 1001n)).toBe(true);
});
});

describe("isRegistrationInGracePeriod", () => {
it.each([
{ now: 999n, expected: false, description: "before expiry" },
{ now: 1000n, expected: true, description: "at expiry" },
{ now: 1050n, expected: true, description: "during grace period" },
{ now: 1100n, expected: true, description: "at expiry + grace period" },
{ now: 1101n, expected: false, description: "after expiry + grace period" },
])("returns $expected when $description", ({ now, expected }) => {
expect(isRegistrationInGracePeriod({ expiry, gracePeriod }, now)).toBe(expected);
});

it("returns false when expiry is null", () => {
expect(isRegistrationInGracePeriod({ expiry: null, gracePeriod }, 1050n)).toBe(false);
});

it("returns false when grace period is null", () => {
expect(isRegistrationInGracePeriod({ expiry, gracePeriod: null }, 1050n)).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export function isRegistrationFullyExpired(info: RegistrationExpiryInfo, now: bi
// no expiry, never expired
Comment thread
tk-o marked this conversation as resolved.
if (info.expiry == null) return false;

// otherwise it is expired if now >= expiry + grace
return now >= info.expiry + (info.gracePeriod ?? 0n);
// otherwise it is expired if now > expiry + grace
return now > info.expiry + (info.gracePeriod ?? 0n);
}

/**
Comment thread
tk-o marked this conversation as resolved.
Expand All @@ -34,5 +34,5 @@ export function isRegistrationInGracePeriod(info: RegistrationExpiryInfo, now: b
if (info.expiry == null) return false;
if (info.gracePeriod == null) return false;

return info.expiry <= now && info.expiry + info.gracePeriod > now;
return info.expiry <= now && info.expiry + info.gracePeriod >= now;
Comment thread
tk-o marked this conversation as resolved.
}
Loading