Skip to content
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
139 changes: 80 additions & 59 deletions src/tasks/generatePublishTx.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import Listr from "listr";
import { ethers } from "ethers";
import {


getEthereumUrl
} from "../utils/getEthereumUrl.js";
import { getPublishTxLink } from "../utils/getLinks.js";
import { addReleaseTx } from "../utils/releaseRecord.js";
import { defaultDir, YargsError } from "../params.js";
import { CliGlobalOptions, ListrContextBuildAndPublish } from "../types.js";
import { readManifest } from "../files/index.js";
import { ApmRepository } from "@dappnode/toolkit";
import registryAbi from "../contracts/ApmRegistryAbi.json" assert { type: "json" };
import { semverToArray } from "../utils/semverToArray.js";
import repoAbi from "../contracts/RepoAbi.json" assert { type: "json" };
import { getEthereumUrl } from "../utils/getEthereumUrl.js";

const isZeroAddress = (address: string): boolean => parseInt(address) === 0;

Expand All @@ -40,9 +35,8 @@ export function generatePublishTx({
developerAddress?: string;
ethProvider: string;
} & CliGlobalOptions): Listr<ListrContextBuildAndPublish> {
// Init APM instance
const ethereumUrl = getEthereumUrl(ethProvider);
const apm = new ApmRepository(ethereumUrl);

const provider = new ethers.JsonRpcProvider(getEthereumUrl(ethProvider))

// Load manifest ##### Verify manifest object
const { manifest } = readManifest({ dir });
Expand All @@ -60,10 +54,11 @@ export function generatePublishTx({
{
title: "Generate transaction",
task: async ctx => {
try {
const repository = await apm.getRepoContract(ensName);
isValidENSName(ensName);
const repository = await provider.resolveName(ensName);
if (repository) {
ctx.txData = {
to: await repository.getAddress(),
to: repository,
value: 0,
data: encodeNewVersionCall({
version: currentVersion,
Expand All @@ -75,24 +70,22 @@ export function generatePublishTx({
currentVersion,
releaseMultiHash
};
} catch (e) {
if (e.message.includes("Could not resolve name")) {
try {
const registryAddress = await new ethers.JsonRpcProvider(
ethereumUrl
).resolveName(ensName.split(".").slice(1).join("."));
if (!registryAddress)
throw new Error("Registry not found for " + ensName);

// If repo does not exist, create a new repo and push version
// A developer address must be provided by the option -a or --developer_address.
if (
!developerAddress ||
!ethers.isAddress(developerAddress) ||
isZeroAddress(developerAddress)
) {
throw new YargsError(
`A new Aragon Package Manager Repo for ${ensName} must be created.
} else {
const registryAddress = await provider.resolveName(ensName.split(".").slice(1).join("."));
if (!registryAddress)
throw Error(
`There must exist a registry for DNP name ${ensName}`
);

// If repo does not exist, create a new repo and push version
// A developer address must be provided by the option -a or --developer_address.
if (
!developerAddress ||
!ethers.isAddress(developerAddress) ||
isZeroAddress(developerAddress)
) {
throw new YargsError(
`A new Aragon Package Manager Repo for ${ensName} must be created.
You must specify the developer address that will control it

with ENV:
Expand All @@ -103,31 +96,24 @@ with command option:

dappnodesdk publish [type] --developer_address 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B
`
);
}

ctx.txData = {
to: registryAddress,
value: 0,
data: encodeNewRepoWithVersionCall({
name: shortName,
developerAddress,
version: currentVersion,
contractAddress,
contentURI
}),
gasLimit: 1100000,
ensName,
currentVersion,
releaseMultiHash,
developerAddress
};
} catch (e) {
throw Error(
`There must exist a registry for DNP name ${ensName}`
);
}
} else throw e;
);
}
ctx.txData = {
to: registryAddress,
value: 0,
data: encodeNewRepoWithVersionCall({
name: shortName,
developerAddress,
version: currentVersion,
contractAddress,
contentURI
}),
gasLimit: 1100000,
ensName,
currentVersion,
releaseMultiHash,
developerAddress
};
}

/**
Expand All @@ -145,17 +131,14 @@ with command option:
);
}

// Utils


/**
* newVersion(
* uint16[3] _newSemanticVersion,
* address _contractAddress,
* bytes _contentURI
* )
*/
export function encodeNewVersionCall({
export function encodeNewVersionCall({
version,
contractAddress,
contentURI
Expand Down Expand Up @@ -203,3 +186,41 @@ export function encodeNewRepoWithVersionCall({
contentURI // bytes _contentURI
]);
}
/**
*checks if the name is a valid ENS name. Returns all reasons why the name is not valid if it is not valid.
* - ENS name must be between 1 and 63 characters.
* - ENS name must contain only lowercase alphanumeric characters(a-z), hyphens(-) and dots(.).
* - Labels must not start or end with a hyphen.
* - Labels must not contain consecutive hyphens.
* - Last label must be ".eth".

*/
export function isValidENSName(name: string): void {
const invalidMessages: string[] = [];

// Length Check
if (name.length < 3 || name.length > 63) {
invalidMessages.push('Length must be between 3 and 63 characters.');
}

// Character Check
if (!/^[a-z0-9-.]+$/.test(name)) {
invalidMessages.push('Contains forbidden characters.');
}

// Hyphen Placement Check
if (name.startsWith("-") || name.endsWith("-") || name.includes("--")) {
invalidMessages.push('Hyphen placement is not allowed.');
}

const labels = name.split(".");
// Last Label Check
const tld = labels[labels.length - 1];
if (tld.toLowerCase() !== "eth") {
invalidMessages.push('Last label must be ".eth".');
}

if (invalidMessages.length > 0) {
throw new Error(`Invalid ENS name: ${invalidMessages.join(' ')}`);
}
}
40 changes: 40 additions & 0 deletions test/utils/resolve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import semver from "semver";
import { expect } from "chai";
import { getEthereumUrl } from "../../src/utils/getEthereumUrl";
import { ethers } from "ethers";
import { ApmRepository } from "@dappnode/toolkit";
describe("Apm constructor", function () {
this.timeout(60_000);

const dnpName = "admin.dnp.dappnode.eth";
const parsedProvider = new ethers.JsonRpcProvider(getEthereumUrl("infura"))

it("Should get the contract the registry contract of a DNP name", async () => {
const registry = await parsedProvider.resolveName(dnpName.split(".").slice(1).join("."))
if (!registry) throw Error("no registry");
expect(registry).to.be.a("string", "Contract instance changed");
expect((registry).toLowerCase()).to.equal(
"0x266BFdb2124A68beB6769dC887BD655f78778923".toLowerCase()
);
});

it("Should get the contract the repo contract of a DNP name", async () => {
const repo = await parsedProvider.resolveName(dnpName)
if (!repo) throw Error("no repo");
expect(repo).to.be.a("string", "Contract instance changed");
expect(repo.toLowerCase()).to.equal(
"0xEe66C4765696C922078e8670aA9E6d4F6fFcc455".toLowerCase()
);
});

it("Should get the latest of a DNP name", async () => {
const apm = new ApmRepository(getEthereumUrl("infura"))
const latestVersion = await apm.getVersionAndIpfsHash({ dnpName })

expect(latestVersion.version).to.be.a("string", "Contract instance changed");
expect(Boolean(semver.valid(latestVersion.version))).to.equal(
true,
`Resulting version is not a valid semver: ${semver}`
);
});
});
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3961,4 +3961,4 @@ [email protected]:
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==