Skip to content

[v1.0] Domain data aggregation and validation for use in the migration #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ docker*.tgz

# We don't ever use the generated manifests
.openzeppelin

output
4 changes: 4 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ const config : HardhatUserConfig = {
// TODO upg: add forking, but make an env var to turn it on/off in the upgrade test
mainnet: {
url: `${process.env.MAINNET_RPC_URL}`,
accounts: [
// Read only
`${process.env.TESTNET_PRIVATE_KEY_A}`,
],
gasPrice: 80000000000,
},
sepolia: {
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"lint"
],
"devDependencies": {
"@apollo/client": "^3.5.6",
"@ensdomains/ensjs": "2.1.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.2",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
Expand All @@ -57,6 +58,7 @@
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.11",
"@types/graphql": "^14.5.0",
"@types/mocha": "^9.1.0",
"@types/node": "^18.15.11",
"@zero-tech/eslint-config-cpt": "0.2.7",
Expand All @@ -66,6 +68,7 @@
"ethers": "^6.9.0",
"hardhat": "^2.19.1",
"hardhat-gas-reporter": "^1.0.9",
"react": "^19.1.0",
"semantic-release": "^21.0.1",
"solhint": "^4.0.0",
"solidity-coverage": "^0.8.5",
Expand Down
84 changes: 84 additions & 0 deletions src/utils/migration/01_validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as hre from "hardhat";
import { getDomains } from "./subgraph";
import { Domain, InvalidDomain, User, ValidatedUser } from "./types";
import { getDBAdapter } from "./database";
import { getZNS } from "./zns-contract-data";
import { validateDomain } from "./validate"


const main = async () => {
const [ migrationAdmin ] = await hre.ethers.getSigners();

// Keeping as separate collections from the start will help downstream registration
const rootDomainObjects = await getDomains(true);
const subdomainObjects = await getDomains(false);

console.log(`Found ${rootDomainObjects.length + subdomainObjects.length} domains`);

const env = process.env.ENV_LEVEL;

if (!env) throw Error("No ENV_LEVEL set in .env file");

const zns = await getZNS(migrationAdmin, env);

const validRoots : Array<Domain> = [];
const validSubs : Array<Domain> = [];
const invalidDomains : Array<InvalidDomain> = [];

// Doing this creates strong typing and extensibility that allows
// the below `insertMany` calls to add properties to the object for `_id`
const roots = rootDomainObjects.map((d) => { return d as Domain; });
const subs = subdomainObjects.map((d) => { return d as Domain; });

// Can iterate all at once for simplicity
let index = 0;
for(let domain of [...roots, ...subs]) {
try {
await validateDomain(domain, zns);

if (domain.isWorld) {
validRoots.push({ ...domain } as Domain);
} else {
validSubs.push({ ...domain } as Domain);
}
} catch (e) {
// For debugging we keep invalid domains rather than throw errors
invalidDomains.push({ message: (e as Error).message, domain: domain });
}

console.log(`Processed ${++index} domains`);
}

// Connect to database collection and write user domain data to DB
const dbName = process.env.MONGO_DB_NAME_WRITE;
if (!dbName) throw Error("No DB name given");

const uri = process.env.MONGO_DB_URI_WRITE;
if (!uri) throw Error("No connection string given");

let client = (await getDBAdapter(uri)).db(dbName);

const rootCollName = process.env.MONGO_DB_ROOT_COLL_NAME || "root-domains";
const subCollName = process.env.MONGO_DB_SUB_COLL_NAME || "subdomains";

// To avoid duplicate data, we clear the DB before any inserts
await client.dropCollection(rootCollName);
await client.collection(rootCollName).insertMany(validRoots);

await client.dropCollection(subCollName);
await client.collection(subCollName).insertMany(validSubs);

// Domains that have split ownership will be considered invalid domains
if (invalidDomains.length > 0) {
const invalidCollName = process.env.MONGO_DB_INVALID_COLL_NAME || "invalid-domains";
await client.dropCollection(invalidCollName);
await client.collection(invalidCollName).insertMany(invalidDomains);
}
};

main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exitCode = 1;
});
75 changes: 75 additions & 0 deletions src/utils/migration/02_registration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as hre from "hardhat";
import { Domain } from "./types";
import * as fs from "fs";
import { deployZNS } from "../../../test/helpers";
import { postMigrationValidation, registerDomainsBulk } from "./registration";
import { getZNS } from "./zns-contract-data";
import { ROOTS_FILENAME, SUBS_FILENAME } from "./constants";
import { IZNSContracts } from "../../deploy/campaign/types";
import { getDBAdapter } from "./database";

// Script #2 to be run AFTER validation of the domains with subgraph
const main = async () => {
const [ migrationAdmin, governor, admin ] = await hre.ethers.getSigners();

// Overall flow will be:
// connect to DB
// read all roots from mongodb
// while there are unregistered root domains:
// register a batch

// read all subs with depth 1 from mongodb
// while there are unregistered subdomains:
// register a batch
// read all subs with depth 2 from mongodb
// while there are unregistered subdomains:
// register a batch
// read all subs with depth 3 from mongodb
// while there are unregistered subdomains:
// register a batch

// During above we will pack transactions with to always have 50 domains
// so if only 45 root domains remain at the end, we will also send the first 5 depth 1 subdomains

// Steps to register a batch will mean using the Safe REST API to create a transaction
// for the owning safe that calls `registerRootDomainBulk` or `registerSubdomainBulk`
// Then we will wait for the transaction to be executed
// Technically we could also sign each tx and execute this way

let zns : IZNSContracts;

const env = process.env.ENV_LEVEL;

if (!env) throw Error("No ENV_LEVEL set in .env file");

// Get instance of ZNS from DB
zns = await getZNS(migrationAdmin, env);

// Connect to database collection and write user domain data to DB
const dbName = process.env.MONGO_DB_NAME_WRITE;
if (!dbName) throw Error("No DB name given");

const uri = process.env.MONGO_DB_URI_WRITE;
if (!uri) throw Error("No connection string given");

let client = (await getDBAdapter(uri)).db(dbName);

const rootCollName = process.env.MONGO_DB_ROOT_COLL_NAME || "root-domains";

// Get all documents from collection
const domains = await client.collection(rootCollName).find().toArray();

console.log(domains.length);

const startTime = Date.now();

// How many domains we will register in a single transaction
const sliceSize = 50;

process.exit(0);
};

main().catch(error => {
console.error(error);
process.exitCode = 1;
});
48 changes: 48 additions & 0 deletions src/utils/migration/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { MongoClient, ServerApiVersion } from "mongodb";

export let dbVersion : string;

export const getDBAdapter = async (
connectionString : string
): Promise<MongoClient> => {
const mongoClient = new MongoClient(
connectionString,
{
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true,
}
}
);

return await mongoClient.connect();
}

export const getZNSFromDB = async () => {
let version;
let uri;
let dbName;

version = process.env.MONGO_DB_VERSION;
uri = process.env.MONGO_DB_URI;
dbName = process.env.MONGO_DB_NAME;

if (!uri) {
throw new Error("Failed to connect: missing MongoDB URI or version");
}

let dbAdapter = await getDBAdapter(uri);

if(!dbName) {
throw new Error(`Failed to connect: database "${dbName}" not found`);
}

const db = await dbAdapter.db(dbName);

let zns = await db.collection("contracts").find(
{ version }
).toArray();

return zns;
};
Loading