diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index cda56cd..7f03715 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/contracts/OIDAccessManager.sol b/contracts/OIDAccessManager.sol index 92dc5ba..8258adb 100644 --- a/contracts/OIDAccessManager.sol +++ b/contracts/OIDAccessManager.sol @@ -5,6 +5,10 @@ pragma solidity 0.8.26; import {AccessManagerUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagerUpgradeable.sol"; contract OIDAccessManager is AccessManagerUpgradeable { + uint64 public constant APPLICATION_MANAGER_ROLE = 1; + uint64 public constant ATTESTATION_MANAGER_ROLE = 2; + uint64 public constant PERMISSION_MANAGER_ROLE = 3; + function initialize() public initializer { __AccessManager_init(msg.sender); } diff --git a/contracts/OIDPermissionManager.sol b/contracts/OIDPermissionManager.sol index 0704a32..3c81b1f 100644 --- a/contracts/OIDPermissionManager.sol +++ b/contracts/OIDPermissionManager.sol @@ -6,6 +6,7 @@ import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManage import {IEAS} from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol"; import {Attestation} from "@ethereum-attestation-service/eas-contracts/contracts/Common.sol"; import {IAccessManager} from "@openzeppelin/contracts/access/manager/IAccessManager.sol"; +import {OIDAccessManager} from "./OIDAccessManager.sol"; contract OIDPermissionManager is IOIDPermissionManager, AccessManaged { error UnauthorizedAccess(address caller); @@ -64,7 +65,11 @@ contract OIDPermissionManager is IOIDPermissionManager, AccessManaged { } function _isPermissionManager() internal view returns (bool) { - (bool isMember, ) = IAccessManager(authority()).hasRole(3, msg.sender); + OIDAccessManager access = OIDAccessManager(authority()); + (bool isMember, ) = access.hasRole( + access.PERMISSION_MANAGER_ROLE(), + msg.sender + ); return isMember; } diff --git a/contracts/OIDResolver.sol b/contracts/OIDResolver.sol index 97be5a3..e484b7a 100644 --- a/contracts/OIDResolver.sol +++ b/contracts/OIDResolver.sol @@ -6,7 +6,7 @@ import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/acce import {IEAS} from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol"; import {Attestation} from "@ethereum-attestation-service/eas-contracts/contracts/Common.sol"; import {SchemaResolver} from "@ethereum-attestation-service/eas-contracts/contracts/resolver/SchemaResolver.sol"; -import {IAccessManager} from "@openzeppelin/contracts/access/manager/IAccessManager.sol"; +import {OIDAccessManager} from "./OIDAccessManager.sol"; contract OIDResolver is SchemaResolver, AccessManagedUpgradeable { error UnauthorizedAttester(address attester); @@ -47,7 +47,11 @@ contract OIDResolver is SchemaResolver, AccessManagedUpgradeable { } function _checkAttester(address attester) internal virtual { - (bool isMember, ) = IAccessManager(authority()).hasRole(2, attester); + OIDAccessManager authority = OIDAccessManager(authority()); + (bool isMember, ) = authority.hasRole( + authority.ATTESTATION_MANAGER_ROLE(), + attester + ); if (!isMember) { revert UnauthorizedAttester(attester); } diff --git a/hardhat.config.ts b/hardhat.config.ts index 7aa31b5..902f9ad 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,9 +1,14 @@ import { type HardhatUserConfig, vars } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox-viem"; import "@nomiclabs/hardhat-solhint"; +import { generatePrivateKey } from "viem/accounts"; -const PRIVATE_KEY = vars.get("PRIVATE_KEY"); -const ETHERSCAN_API_KEY = vars.get("ETHERSCAN_API_KEY"); +const PRIVATE_KEY = vars.has("PRIVATE_KEY") + ? vars.get("PRIVATE_KEY") + : generatePrivateKey(); +const ETHERSCAN_API_KEY = vars.has("ETHERSCAN_API_KEY") + ? vars.get("ETHERSCAN_API_KEY") + : ""; const config: HardhatUserConfig = { solidity: { diff --git a/package.json b/package.json index 4f54935..2225ffa 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "ethers": "^6.13.1", "hardhat": "^2.14.0", "hardhat-gas-reporter": "^1.0.8", + "husky": "^9.1.5", "solidity-coverage": "^0.8.0", "ts-node": ">=8.0.0", "typescript": "~5.0.4", diff --git a/test/ApplicationManager.ts b/test/ApplicationManager.ts index 5ef27b3..3d753c2 100644 --- a/test/ApplicationManager.ts +++ b/test/ApplicationManager.ts @@ -9,6 +9,7 @@ import { toFunctionSelector, } from "viem"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { deployAccessManager } from "../utils/deployAccessManager"; interface Application { id?: bigint; @@ -16,8 +17,6 @@ interface Application { account: Address; } -const MANAGER_ROLE = 1n; - const CREATE_APPLICATION_SELECTOR = toFunctionSelector( "createApplication((string, address))", ); @@ -42,13 +41,23 @@ describe("ApplicationManager", () => { // Contracts are deployed using the first signer/account by default const [deployer, manager, otherAccount] = await hre.viem.getWalletClients(); - const access = await hre.viem.deployContract("OIDAccessManager"); - await access.write.initialize(); + const access = await deployAccessManager(deployer); + + const APPLICATION_MANAGER_ROLE = + await access.read.APPLICATION_MANAGER_ROLE(); - await access.write.grantRole([MANAGER_ROLE, manager.account.address, 0]); + await access.write.grantRole([ + APPLICATION_MANAGER_ROLE, + manager.account.address, + 0, + ]); - // Assign deployer to MANAGER_ROLE for simplicity - await access.write.grantRole([MANAGER_ROLE, deployer.account.address, 0]); + // Assign deployer to APPLICATION_MANAGER_ROLE for simplicity + await access.write.grantRole([ + APPLICATION_MANAGER_ROLE, + deployer.account.address, + 0, + ]); const contract = await hre.viem.deployContract("ApplicationManager", [ access.address, @@ -61,7 +70,7 @@ describe("ApplicationManager", () => { UPDATE_APPLICATION_SELECTOR, DELETE_APPLICATION_SELECTOR, ], - MANAGER_ROLE, + APPLICATION_MANAGER_ROLE, ]); const publicClient = await hre.viem.getPublicClient(); @@ -73,6 +82,7 @@ describe("ApplicationManager", () => { manager, otherAccount, publicClient, + APPLICATION_MANAGER_ROLE, }; } diff --git a/test/OIDAccessManager.ts b/test/OIDAccessManager.ts index 11ec0c6..738280d 100644 --- a/test/OIDAccessManager.ts +++ b/test/OIDAccessManager.ts @@ -30,5 +30,17 @@ describe("OIDAccessManager", () => { await contract.read.hasRole([ADMIN_ROLE, deployer.account.address]), ).to.deep.eq([true, 0]); }); + it("Should have APPLICATION_MANAGER_ROLE", async () => { + const { contract, deployer } = await loadFixture(deploy); + expect(await contract.read.APPLICATION_MANAGER_ROLE()).to.eq(1n); + }); + it("Should have ATTESTATION_MANAGER_ROLE", async () => { + const { contract, deployer } = await loadFixture(deploy); + expect(await contract.read.ATTESTATION_MANAGER_ROLE()).to.eq(2n); + }); + it("Should have PERMISSION_MANAGER_ROLE", async () => { + const { contract, deployer } = await loadFixture(deploy); + expect(await contract.read.PERMISSION_MANAGER_ROLE()).to.eq(3n); + }); }); }); diff --git a/test/OIDPermissionManager.ts b/test/OIDPermissionManager.ts index 010a31f..deb818c 100644 --- a/test/OIDPermissionManager.ts +++ b/test/OIDPermissionManager.ts @@ -20,10 +20,8 @@ import { } from "viem"; import { clientToSigner } from "../utils/clientToSigner"; import { SIMPLE_SCHEMA } from "../utils/constants"; -import { ROLES } from "../utils/roles"; - -const { ID: PERMISSION_MANAGER_ROLE_ID, LABEL: PERMISSION_MANAGER_ROLE_LABEL } = - ROLES.PERMISSION_MANAGER; +import { deployAccessManager } from "../utils/deployAccessManager"; +import { deployEAS, deploySchema } from "../utils/deployEAS"; describe("OIDPermissionManager", () => { async function attest( @@ -54,27 +52,6 @@ describe("OIDPermissionManager", () => { return { attestationUID }; } - async function deployEAS(deployer: Client) { - const registry = await hre.viem.deployContract("SchemaRegistry"); - const eas = await hre.viem.deployContract("EAS", [registry.address]); - - // Need to mix in ethers - const signer = clientToSigner(deployer); - const schemaRegistry = new SchemaRegistry(registry.address); - schemaRegistry.connect(signer); - const tx = await schemaRegistry.register({ schema: SIMPLE_SCHEMA }); - await tx.wait(); - - const events = await registry.getEvents.Registered(); - const schemaUID = events[0].args.uid as Address; - - return { - registry, - eas, - schemaUID, - }; - } - // We define a fixture to reuse the same setup in every test. // We use loadFixture to run this setup once, snapshot that state, // and reset Hardhat Network to that snapshot in every test. @@ -84,7 +61,12 @@ describe("OIDPermissionManager", () => { await hre.viem.getWalletClients(); // EAS Deployment - const { registry, eas, schemaUID } = await deployEAS(deployer); + const { registry, eas } = await deployEAS(deployer); + const schemaUID = await deploySchema( + deployer, + registry.address, + SIMPLE_SCHEMA, + ); const { attestationUID } = await attest( attester, recipient.account.address, @@ -94,21 +76,12 @@ describe("OIDPermissionManager", () => { [{ name: "id", value: 1, type: "uint256" }], ); - const access = await hre.viem.deployContract("OIDAccessManager"); - await access.write.initialize(); - await access.write.labelRole([ - PERMISSION_MANAGER_ROLE_ID, - PERMISSION_MANAGER_ROLE_LABEL, - ]); - await access.write.grantRole([ - PERMISSION_MANAGER_ROLE_ID, - manager.account.address, - 0, - ]); + const access = await deployAccessManager(deployer); + const PERMISSION_MANAGER_ROLE = await access.read.PERMISSION_MANAGER_ROLE(); // Assign manager to PERMISSION_MANAGER_ROLE await access.write.grantRole([ - PERMISSION_MANAGER_ROLE_ID, + PERMISSION_MANAGER_ROLE, manager.account.address, 0, ]); diff --git a/test/OIDResolver.ts b/test/OIDResolver.ts index b5dfa8c..cdac03e 100644 --- a/test/OIDResolver.ts +++ b/test/OIDResolver.ts @@ -24,29 +24,10 @@ import { parseSignature, zeroHash, } from "viem"; -import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; - -const ATTESTER_ROLE = 1n; - -const schema = "uint256 id"; - -function generateRandomAddress(): Address { - const randomKey = generatePrivateKey(); - const account = privateKeyToAccount(randomKey); - return account.address; -} - -function clientToSigner(client: Client) { - const { account, chain, transport } = client; - const network = { - chainId: chain.id, - name: chain.name, - ensAddress: chain.contracts?.ensRegistry?.address, - }; - const provider = new BrowserProvider(transport, network); - const signer = new JsonRpcSigner(provider, account.address); - return signer; -} +import { clientToSigner } from "../utils/clientToSigner"; +import { SIMPLE_SCHEMA } from "../utils/constants"; +import { deployAccessManager } from "../utils/deployAccessManager"; +import { deployEAS, deploySchema } from "../utils/deployEAS"; describe("OIDResolver", () => { // We define a fixture to reuse the same setup in every test. @@ -57,41 +38,36 @@ describe("OIDResolver", () => { const [deployer, attester, otherAccount] = await hre.viem.getWalletClients(); - const registry = await hre.viem.deployContract("SchemaRegistry"); + const { eas, registry } = await deployEAS(deployer); - const eas = await hre.viem.deployContract("EAS", [registry.address]); + const authority = await deployAccessManager(deployer); + const ATTESTATION_MANAGER_ROLE = + await authority.read.ATTESTATION_MANAGER_ROLE(); - const access = await hre.viem.deployContract("OIDAccessManager"); - await access.write.initialize(); - - await access.write.grantRole([ATTESTER_ROLE, attester.account.address, 0]); + await authority.write.grantRole([ + ATTESTATION_MANAGER_ROLE, + attester.account.address, + 0, + ]); const resolver = await hre.viem.deployContract("OIDResolver", [ eas.address, ]); - - await resolver.write.initialize([access.address]); + await resolver.write.initialize([authority.address]); const publicClient = await hre.viem.getPublicClient(); - // Need to mix in ethers - const signer = clientToSigner(deployer); - const schemaRegistry = new SchemaRegistry(registry.address); - schemaRegistry.connect(signer); - const tx = await schemaRegistry.register({ - schema, - resolverAddress: resolver.address, - revocable: true, - }); - await tx.wait(); - - const events = await registry.getEvents.Registered(); - const schemaUID = events[0].args.uid as Address; + const schemaUID = await deploySchema( + deployer, + registry.address, + SIMPLE_SCHEMA, + resolver.address, + ); return { registry, eas, - access, + authority, resolver, deployer, attester, @@ -109,9 +85,11 @@ describe("OIDResolver", () => { }); describe("Initialize", () => { - it("Should set AccessManager", async () => { - const { access, resolver } = await loadFixture(deploy); - expect(await resolver.read.authority()).to.eq(getAddress(access.address)); + it("Should set authority", async () => { + const { authority, resolver } = await loadFixture(deploy); + expect(await resolver.read.authority()).to.eq( + getAddress(authority.address), + ); }); }); diff --git a/utils/deployAccessManager.ts b/utils/deployAccessManager.ts new file mode 100644 index 0000000..6fc227f --- /dev/null +++ b/utils/deployAccessManager.ts @@ -0,0 +1,10 @@ +import hre from "hardhat"; +import type { WalletClient } from "viem"; + +export async function deployAccessManager(deployer: WalletClient) { + const contract = await hre.viem.deployContract("OIDAccessManager", [], { + account: deployer.account, + }); + await contract.write.initialize(); + return contract; +} diff --git a/utils/deployEAS.ts b/utils/deployEAS.ts new file mode 100644 index 0000000..8f46ba7 --- /dev/null +++ b/utils/deployEAS.ts @@ -0,0 +1,39 @@ +import { SchemaRegistry } from "@ethereum-attestation-service/eas-sdk"; +import hre from "hardhat"; +import type { + Account, + Address, + Chain, + Client, + Transport, + WalletClient, +} from "viem"; +import { clientToSigner } from "./clientToSigner"; + +export async function deployEAS(deployer: WalletClient) { + const registry = await hre.viem.deployContract("SchemaRegistry", [], { + account: deployer.account, + }); + const eas = await hre.viem.deployContract("EAS", [registry.address], { + account: deployer.account, + }); + return { + registry, + eas, + }; +} + +export async function deploySchema( + deployer: Client, + registryAddress: string, + schema: string, + resolverAddress = "0x0000000000000000000000000000000000000000", + revocable = true, +): Promise
{ + const registry = new SchemaRegistry(registryAddress); + + const signer = clientToSigner(deployer); + registry.connect(signer); + const tx = await registry.register({ schema, resolverAddress, revocable }); + return (await tx.wait()) as Address; +} diff --git a/utils/roles.ts b/utils/roles.ts deleted file mode 100644 index 5f17241..0000000 --- a/utils/roles.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const ROLES = { - APPLICATION_MANAGER: { - ID: 1n, - LABEL: "APPLICATION_MANAGER_ROLE", - }, - ATTESTER: { - ID: 2n, - LABEL: "ATTESTER_ROLE", - }, - PERMISSION_MANAGER: { - ID: 3n, - LABEL: "PERMISSION_MANAGER_ROLE", - }, -}; diff --git a/yarn.lock b/yarn.lock index 8844137..2004c16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2370,6 +2370,7 @@ __metadata: ethers: "npm:^6.13.1" hardhat: "npm:^2.14.0" hardhat-gas-reporter: "npm:^1.0.8" + husky: "npm:^9.1.5" solidity-coverage: "npm:^0.8.0" ts-node: "npm:>=8.0.0" typescript: "npm:~5.0.4" @@ -3770,6 +3771,15 @@ __metadata: languageName: node linkType: hard +"husky@npm:^9.1.5": + version: 9.1.5 + resolution: "husky@npm:9.1.5" + bin: + husky: bin.js + checksum: 10c0/f42efb95a026303eb880898760f802d88409780dd72f17781d2dfc302177d4f80b641cf1f1694f53f6d97c536c7397684133d8c8fe4a4426f7460186a7d1c6b8 + languageName: node + linkType: hard + "iconv-lite@npm:0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24"