diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 67296b3..3e74c76 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/biome.json b/biome.json index a8df9d8..2ecd470 100644 --- a/biome.json +++ b/biome.json @@ -16,7 +16,8 @@ "./cache", "./node_modules", "./coverage", - "./coverage.json" + "./coverage.json", + "./ignition/deployments" ] } } diff --git a/contracts/ApplicationManager.sol b/contracts/ApplicationManager.sol index a285761..220249d 100644 --- a/contracts/ApplicationManager.sol +++ b/contracts/ApplicationManager.sol @@ -2,13 +2,14 @@ pragma solidity 0.8.26; import {IApplicationManager} from "./IApplicationManager.sol"; +import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; -contract ApplicationManager is IApplicationManager { +contract ApplicationManager is IApplicationManager, AccessManaged { mapping(uint => Application) private applications; mapping(address => bool) private addressUsed; uint private nextApplicationId; - constructor() {} + constructor(address initialAuthority) AccessManaged(initialAuthority) {} function applicationExists(uint id) internal view returns (bool) { return applications[id].account != address(0); @@ -18,7 +19,9 @@ contract ApplicationManager is IApplicationManager { return nextApplicationId; } - function createApplication(Application memory newApplication) external { + function createApplication( + Application memory newApplication + ) external restricted { require( !addressUsed[newApplication.account], "Address already used for another application" @@ -35,18 +38,18 @@ contract ApplicationManager is IApplicationManager { function updateApplication( uint id, Application memory updatedApplication - ) external { + ) external restricted { require(applicationExists(id), "Application does not exist"); require( !addressUsed[updatedApplication.account] || applications[id].account == updatedApplication.account, - "Address already used for another application" + "Account used by another application" ); applications[id] = updatedApplication; emit ApplicationUpdated(id, applications[id]); } - function deleteApplication(uint id) external { + function deleteApplication(uint id) external restricted { require(applicationExists(id), "Application does not exist"); addressUsed[applications[id].account] = false; emit ApplicationDeleted(id, applications[id]); diff --git a/contracts/OIDAccessManager.sol b/contracts/OIDAccessManager.sol new file mode 100644 index 0000000..92dc5ba --- /dev/null +++ b/contracts/OIDAccessManager.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +// solhint-disable-next-line max-line-length +import {AccessManagerUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagerUpgradeable.sol"; + +contract OIDAccessManager is AccessManagerUpgradeable { + function initialize() public initializer { + __AccessManager_init(msg.sender); + } +} diff --git a/ignition/modules/Lock.ts b/ignition/modules/Lock.ts deleted file mode 100644 index 1190185..0000000 --- a/ignition/modules/Lock.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; -import { parseEther } from "viem"; - -const JAN_1ST_2030 = 1893456000; -const ONE_GWEI: bigint = parseEther("0.001"); - -const LockModule = buildModule("LockModule", (m) => { - const unlockTime = m.getParameter("unlockTime", JAN_1ST_2030); - const lockedAmount = m.getParameter("lockedAmount", ONE_GWEI); - - const lock = m.contract("Lock", [unlockTime], { - value: lockedAmount, - }); - - return { lock }; -}); - -export default LockModule; diff --git a/ignition/modules/OIDAccessManager.ts b/ignition/modules/OIDAccessManager.ts new file mode 100644 index 0000000..380e747 --- /dev/null +++ b/ignition/modules/OIDAccessManager.ts @@ -0,0 +1,17 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +const OIDAccessManagerModule = buildModule("OIDAccessManagerModule", (m) => { + // const deployer = m.getAccount(0); + + const manager = m.contract("OIDAccessManager", [], {}); + m.call(manager, "initialize", []); + + // m.call(manager, "labelRole", [1n, "APP_MANAGER_ROLE"]); + + // console.log(appManager); + // m.call(manager, "grantRole", [1n, appManager, 0]); + + return { manager }; +}); + +export default OIDAccessManagerModule; diff --git a/package.json b/package.json index 121f01c..7a61a0e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "@nomicfoundation/hardhat-verify": "^2.0.0", "@nomicfoundation/hardhat-viem": "^2.0.0", "@nomiclabs/hardhat-solhint": "^3.1.0", + "@openzeppelin/contracts": "^5.0.2", + "@openzeppelin/contracts-upgradeable": "^5.0.2", "@types/chai": "^4.2.0", "@types/chai-as-promised": "^7.1.6", "@types/mocha": ">=9.1.0", diff --git a/test/ApplicationManager.ts b/test/ApplicationManager.ts index 522ee57..36c353d 100644 --- a/test/ApplicationManager.ts +++ b/test/ApplicationManager.ts @@ -1,7 +1,13 @@ import { loadFixture } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers"; import { expect } from "chai"; import hre from "hardhat"; -import { type Address, getAddress, parseUnits } from "viem"; +import { + type Address, + type WalletClient, + getAddress, + parseUnits, + toFunctionSelector, +} from "viem"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; interface Application { @@ -9,34 +15,66 @@ interface Application { account: Address; } +const MANAGER_ROLE = 1n; + +const CREATE_APPLICATION_SELECTOR = toFunctionSelector( + "createApplication((string, address))", +); +const UPDATE_APPLICATION_SELECTOR = toFunctionSelector( + "updateApplication(uint256, (string, address))", +); +const DELETE_APPLICATION_SELECTOR = toFunctionSelector( + "deleteApplication(uint256)", +); + +function generateRandomAddress(): Address { + const randomKey = generatePrivateKey(); + const account = privateKeyToAccount(randomKey); + return account.address; +} + describe("ApplicationManager", () => { // 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. async function deploy() { // Contracts are deployed using the first signer/account by default - const [owner, otherAccount, app1Account] = - await hre.viem.getWalletClients(); + const [deployer, manager, otherAccount] = await hre.viem.getWalletClients(); + + const access = await hre.viem.deployContract("OIDAccessManager"); + await access.write.initialize(); + + await access.write.grantRole([MANAGER_ROLE, manager.account.address, 0]); - const contract = await hre.viem.deployContract("ApplicationManager"); + // Assign deployer to MANAGER_ROLE for simplicity + await access.write.grantRole([MANAGER_ROLE, deployer.account.address, 0]); + + const contract = await hre.viem.deployContract("ApplicationManager", [ + access.address, + ]); + + await access.write.setTargetFunctionRole([ + contract.address, + [ + CREATE_APPLICATION_SELECTOR, + UPDATE_APPLICATION_SELECTOR, + DELETE_APPLICATION_SELECTOR, + ], + MANAGER_ROLE, + ]); const publicClient = await hre.viem.getPublicClient(); return { + access, contract, - owner, + deployer, + manager, otherAccount, - app1Account, publicClient, }; } - function generateRandomAddress(): string { - const randomKey = generatePrivateKey(); - const account = privateKeyToAccount(randomKey); - return account.address; - } - describe("Deployment", () => { it("Should set the nextxApplicationId to 0", async () => { const { contract } = await loadFixture(deploy); @@ -44,319 +82,338 @@ describe("ApplicationManager", () => { parseUnits("0", 0), ); }); - }); - - describe("createApplication", () => { - // biome-ignore lint/suspicious/noExplicitAny: - let contract: any; - let app: Application; - const account = generateRandomAddress() as Address; - beforeEach(async () => { - const fixture = await loadFixture(deploy); - contract = fixture.contract; - app = { - name: "app", - account, - }; - }); - - it("Should accept Application as input", async () => { - expect(await contract.write.createApplication([app])).to.be.string; - }); - it("Should increment nextApplicationId", async () => { - const applicationId0 = await contract.read.getNextApplicationId(); - expect(applicationId0).to.equal(parseUnits("0", 0)); - await contract.write.createApplication([app]); - const applicationId1 = await contract.read.getNextApplicationId(); - expect(applicationId1).to.equal(parseUnits("1", 0)); - }); - it("Should create a new Application", async () => { - const applicationId = await contract.read.getNextApplicationId(); - await contract.write.createApplication([app]); - expect(await contract.read.getApplication([applicationId])).to.contain( - app, - ); - }); - it("Should revert if the account address is already used", async () => { - await contract.write.createApplication([app]); - await expect(contract.write.createApplication([app])).to.be.rejectedWith( - "Address already used for another application", + it("Should set the authority", async () => { + const { contract, access } = await loadFixture(deploy); + expect(await contract.read.authority()).to.equal( + getAddress(access.address), ); }); - it("Should emit the ApplicationCreated event with the new application's details", async () => { - const applicationId = await contract.read.getNextApplicationId(); - const txHash = await contract.write.createApplication([app]); - - const events = await contract.getEvents.ApplicationCreated(); - - expect(events.length).to.be.equal(1); - - const event = events[0]; - expect(event.eventName).to.be.equal("ApplicationCreated"); - expect(event.transactionHash).to.be.equal(txHash); - expect(event.args.id).to.be.equal(applicationId); - expect(event.args.application).to.contain(app); - }); }); - describe("updateApplication", () => { + describe("functions", () => { + const account = generateRandomAddress(); // biome-ignore lint/suspicious/noExplicitAny: let contract: any; + let app: Application; - const account = generateRandomAddress() as Address; + let manager: WalletClient; + let otherAccount: WalletClient; beforeEach(async () => { const fixture = await loadFixture(deploy); contract = fixture.contract; - app = { name: "app", account, }; - - await contract.write.createApplication([app]); + manager = fixture.manager; + otherAccount = fixture.otherAccount; }); - it("Should accept Application and id as inputs", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - expect(await contract.write.updateApplication([applicationId, app])).to.be - .string; - }); - it("Should update the application in the applications mapping with the provided applicationId to have the application data", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - await contract.write.updateApplication([ - applicationId, - { account: app.account, name: "new app name" }, - ]); - expect(await contract.read.getApplication([applicationId])).to.contain({ - account: app.account, - name: "new app name", + describe("createApplication", () => { + describe("Manager", () => { + it("Should increment nextApplicationId", async () => { + const applicationId0 = await contract.read.getNextApplicationId(); + expect(applicationId0).to.equal(parseUnits("0", 0)); + + await contract.write.createApplication([app], { + account: manager.account?.address, + }); + const applicationId1 = await contract.read.getNextApplicationId(); + expect(applicationId1).to.equal(parseUnits("1", 0)); + }); + it("Should revert if the account address is already used", async () => { + await contract.write.createApplication([app], { + account: manager.account?.address, + }); + await expect( + contract.write.createApplication([app], { + account: manager.account?.address, + }), + ).to.be.rejectedWith("Address already used for another application"); + }); + it("Should create a new Application", async () => { + const applicationId = await contract.read.getNextApplicationId(); + await contract.write.createApplication([app], { + account: manager.account?.address, + }); + expect( + await contract.read.getApplication([applicationId]), + ).to.contain(app); + }); + it("Should emit the ApplicationCreated event with the new application's details", async () => { + const applicationId = await contract.read.getNextApplicationId(); + const txHash = await contract.write.createApplication([app], { + account: manager.account?.address, + }); + + const events = await contract.getEvents.ApplicationCreated(); + + expect(events.length).to.be.equal(1); + + const event = events[0]; + expect(event.eventName).to.be.equal("ApplicationCreated"); + expect(event.transactionHash).to.be.equal(txHash); + expect(event.args.id).to.be.equal(applicationId); + expect(event.args.application).to.contain(app); + }); }); - }); - it("Should revert if the account address is already used", async () => { - const applicationId1 = await contract.read.getNextApplicationId(); - expect(applicationId1).to.equal(parseUnits("1", 0)); - await contract.write.createApplication([ - { - account: generateRandomAddress() as Address, - name: "new app", - }, - ]); - - await expect( - contract.write.updateApplication([applicationId1, app]), - ).to.be.rejectedWith("Address already used for another application"); - }); - it("Should revert if the application does not exist", async () => { - const nonExistentId = parseUnits("999", 0); - await expect( - contract.write.updateApplication([nonExistentId, app]), - ).to.be.rejectedWith("Application does not exist"); - }); - - it("Should emit the ApplicationUpdated event with the updated application's details", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - const txHash = await contract.write.updateApplication([ - applicationId, - app, - ]); - const events = await contract.getEvents.ApplicationUpdated(); - expect(events.length).to.be.equal(1); - - const event = events[0]; - expect(event.eventName).to.be.equal("ApplicationUpdated"); - expect(event.transactionHash).to.be.equal(txHash); - expect(event.args.id).to.be.equal(applicationId); - expect(event.args.application).to.contain(app); - }); - }); - - describe("deleteApplication", () => { - // biome-ignore lint/suspicious/noExplicitAny: - let contract: any; - let app: Application; - const account = privateKeyToAccount(generatePrivateKey()).address; - - beforeEach(async () => { - const fixture = await loadFixture(deploy); - contract = fixture.contract; - - app = { - name: "app", - account, - }; - await contract.write.createApplication([app]); - }); - - it("Should accept id as input", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - expect(await contract.write.deleteApplication([applicationId])).to.be - .string; - }); - it("Should delete the application from the applications mapping with the provided applicationId", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - await contract.write.deleteApplication([applicationId]); - await expect( - contract.read.getApplication([applicationId]), - ).to.be.rejectedWith("Application does not exist"); - }); - it("Should revert if the application does not exist", async () => { - const nonExistentId = parseUnits("999", 0); - await expect( - contract.write.deleteApplication([nonExistentId]), - ).to.be.rejectedWith("Application does not exist"); + describe("Other Account", () => { + it("Should revert with AccessManagedUnauthorized", async () => { + await expect( + contract.write.createApplication([app], { + account: otherAccount.account?.address, + }), + ).to.be.rejectedWith( + `AccessManagedUnauthorized("${getAddress(otherAccount.account?.address as Address)}")`, + ); + }); + }); }); - it("Should emit the ApplicationDeleted event with the deleted application's details", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - const txHash = await contract.write.deleteApplication([applicationId]); + describe("updateApplication", () => { + beforeEach(async () => { + await contract.write.createApplication([app], { + account: manager.account?.address, + }); + }); - const events = await contract.getEvents.ApplicationDeleted(); - expect(events.length).to.be.equal(1); + describe("Manager", () => { + it("Should update the application", async () => { + const applicationId = + (await contract.read.getNextApplicationId()) - parseUnits("1", 0); + const newName = "new app name"; + await contract.write.updateApplication( + [applicationId, { account: app.account, name: newName }], + { account: manager.account?.address }, + ); + expect( + await contract.read.getApplication([applicationId]), + ).to.contain({ + account: app.account, + name: newName, + }); + }); + it("Should revert if the account exists", async () => { + const applicationId1 = await contract.read.getNextApplicationId(); + await contract.write.createApplication( + [ + { + account: generateRandomAddress(), + name: "new app", + }, + ], + { + account: manager.account?.address, + }, + ); + + await expect( + contract.write.updateApplication([applicationId1, app], { + account: manager.account?.address, + }), + ).to.be.rejectedWith("Account used by another application"); + }); + it("Should revert if the application does not exist", async () => { + const nonExistentId = parseUnits("999", 0); + await expect( + contract.write.updateApplication([nonExistentId, app], { + account: manager.account?.address, + }), + ).to.be.rejectedWith("Application does not exist"); + }); + it("Should emit the ApplicationUpdated event", async () => { + const applicationId = + (await contract.read.getNextApplicationId()) - parseUnits("1", 0); + const txHash = await contract.write.updateApplication( + [applicationId, app], + { account: manager.account?.address }, + ); + const events = await contract.getEvents.ApplicationUpdated(); + expect(events.length).to.be.equal(1); + + const event = events[0]; + expect(event.eventName).to.be.equal("ApplicationUpdated"); + expect(event.transactionHash).to.be.equal(txHash); + expect(event.args.id).to.be.equal(applicationId); + expect(event.args.application).to.contain(app); + }); + }); - const event = events[0]; - expect(event.eventName).to.be.equal("ApplicationDeleted"); - expect(event.transactionHash).to.be.equal(txHash); - expect(event.args.id).to.be.equal(applicationId); - expect(event.args.application).to.contain(app); + describe("Other Account", () => { + it("Should revert with AccessManagedUnauthorized", async () => { + const applicationId = + (await contract.read.getNextApplicationId()) - parseUnits("1", 0); + const newName = "new app name"; + await expect( + contract.write.updateApplication( + [applicationId, { account: app.account, name: newName }], + { account: otherAccount.account?.address }, + ), + ).to.be.rejectedWith( + `AccessManagedUnauthorized("${getAddress(otherAccount.account?.address as Address)}")`, + ); + }); + }); }); - }); - describe("getApplication", () => { - // biome-ignore lint/suspicious/noExplicitAny: - let contract: any; - let app: Application; - const account = privateKeyToAccount(generatePrivateKey()).address; + describe("deleteApplication", () => { + const applicationId = 0n; - beforeEach(async () => { - const fixture = await loadFixture(deploy); - contract = fixture.contract; - - app = { - name: "app", - account, - }; + beforeEach(async () => { + await contract.write.createApplication([app], { + account: manager.account?.address, + }); + }); - await contract.write.createApplication([app]); - }); + describe("Manager", () => { + it("Should delete the application", async () => { + await contract.write.deleteApplication([applicationId], { + account: manager.account?.address, + }); + await expect( + contract.read.getApplication([applicationId]), + ).to.be.rejectedWith("Application does not exist"); + }); + it("Should revert if the application does not exist", async () => { + const nonExistentId = parseUnits("999", 0); + await expect( + contract.write.deleteApplication([nonExistentId], { + account: manager.account?.address, + }), + ).to.be.rejectedWith("Application does not exist"); + }); + + it("Should emit the ApplicationDeleted event", async () => { + const txHash = await contract.write.deleteApplication( + [applicationId], + { account: manager.account?.address }, + ); + + const events = await contract.getEvents.ApplicationDeleted(); + expect(events.length).to.be.equal(1); + + const event = events[0]; + expect(event.eventName).to.be.equal("ApplicationDeleted"); + expect(event.transactionHash).to.be.equal(txHash); + expect(event.args.id).to.be.equal(applicationId); + expect(event.args.application).to.contain(app); + }); + }); - it("Should accept id as input", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - expect(await contract.write.getApplication([applicationId])).to.be.string; - }); - it("Should return the application from the applications mapping with the provided applicationId", async () => { - const applicationId = - (await contract.read.getNextApplicationId()) - parseUnits("1", 0); - expect(await contract.read.getApplication([applicationId])).to.contains( - app, - ); - }); - it("Should revert if the application does not exist", async () => { - const nonExistentId = parseUnits("999", 0); - await expect( - contract.read.getApplication([nonExistentId]), - ).to.be.rejectedWith("Application does not exist"); + describe("Other Account", () => { + it("Should revert with AccessManagedUnauthorized", async () => { + await expect( + contract.write.deleteApplication([applicationId], { + account: otherAccount.account?.address, + }), + ).to.be.rejectedWith( + `AccessManagedUnauthorized("${getAddress(otherAccount.account?.address as Address)}")`, + ); + }); + }); }); - }); - describe("getApplications", () => { - // biome-ignore lint/suspicious/noExplicitAny: - let contract: any; - let apps: Application[]; - // biome-ignore lint/suspicious/noExplicitAny: - let fixture: any; - - beforeEach(async () => { - fixture = await loadFixture(deploy); - contract = fixture.contract; - apps = []; - for (let i = 0; i < 8; i++) { - const app = { - name: `app${i}`, - account: generateRandomAddress() as Address, - }; + describe("getApplication", () => { + beforeEach(async () => { await contract.write.createApplication([app]); - apps.push(app); - } + }); + it("Should return the application", async () => { + const applicationId = + (await contract.read.getNextApplicationId()) - parseUnits("1", 0); + expect(await contract.read.getApplication([applicationId])).to.contains( + app, + ); + }); + it("Should revert if the application does not exist", async () => { + const nonExistentId = parseUnits("999", 0); + await expect( + contract.read.getApplication([nonExistentId]), + ).to.be.rejectedWith("Application does not exist"); + }); }); - it("Should accept start and limit as inputs", async () => { - const start = parseUnits("0", 0); - const limit = parseUnits("7", 0); - - expect(await contract.write.getApplications([start, limit])).to.be.string; - }); + describe("getApplications", () => { + let apps: Application[]; + + beforeEach(async () => { + apps = []; + for (let i = 0; i < 8; i++) { + const app = { + name: `app${i}`, + account: generateRandomAddress(), + }; + await contract.write.createApplication([app]); + apps.push(app); + } + }); - it("Should return an array of Application structs starting from the start index and containing up to limit entries.", async () => { - const start = parseUnits("0", 0); - const limit = parseUnits("6", 0); - const returnedApps = await contract.read.getApplications([start, limit]); - expect(returnedApps.length).to.equal(6); - expect(returnedApps).to.deep.include.members([ - apps[0], - apps[1], - apps[2], - apps[3], - apps[4], - apps[5], - ]); - }); + it("Should return an array of Applications.", async () => { + const start = parseUnits("0", 0); + const limit = parseUnits("6", 0); + const returnedApps = await contract.read.getApplications([ + start, + limit, + ]); + expect(returnedApps.length).to.equal(6); + expect(returnedApps).to.deep.include.members([ + apps[0], + apps[1], + apps[2], + apps[3], + apps[4], + apps[5], + ]); + }); - it("Should return fewer than limit entries if there are not enough applications", async () => { - await contract.write.deleteApplication([parseUnits("7", 0)]); - const start = parseUnits("6", 0); - const limit = parseUnits("2", 0); - const returnedApps = await contract.read.getApplications([start, limit]); - expect(returnedApps.length).to.equal(1); - expect(returnedApps).to.deep.include.members([apps[6]]); - }); + it("Should return fewer than limit entries if there are not enough applications", async () => { + await contract.write.deleteApplication([parseUnits("7", 0)]); + const start = parseUnits("6", 0); + const limit = parseUnits("2", 0); + const returnedApps = await contract.read.getApplications([ + start, + limit, + ]); + expect(returnedApps.length).to.equal(1); + expect(returnedApps).to.deep.include.members([apps[6]]); + }); - it("Should return no applications if all within range are deleted", async () => { - await contract.write.deleteApplication([parseUnits("0", 0)]); - await contract.write.deleteApplication([parseUnits("1", 0)]); - await contract.write.deleteApplication([parseUnits("2", 0)]); - await contract.write.deleteApplication([parseUnits("3", 0)]); - await contract.write.deleteApplication([parseUnits("4", 0)]); - await contract.write.deleteApplication([parseUnits("5", 0)]); - await contract.write.deleteApplication([parseUnits("6", 0)]); - await contract.write.deleteApplication([parseUnits("7", 0)]); - const start = parseUnits("0", 0); - const limit = parseUnits("7", 0); - const returnedApps = await contract.read.getApplications([start, limit]); - expect(returnedApps.length).to.equal(0); + it("Should return no applications if all within range are deleted", async () => { + await contract.write.deleteApplication([parseUnits("0", 0)]); + await contract.write.deleteApplication([parseUnits("1", 0)]); + await contract.write.deleteApplication([parseUnits("2", 0)]); + await contract.write.deleteApplication([parseUnits("3", 0)]); + await contract.write.deleteApplication([parseUnits("4", 0)]); + await contract.write.deleteApplication([parseUnits("5", 0)]); + await contract.write.deleteApplication([parseUnits("6", 0)]); + await contract.write.deleteApplication([parseUnits("7", 0)]); + const start = parseUnits("0", 0); + const limit = parseUnits("7", 0); + const returnedApps = await contract.read.getApplications([ + start, + limit, + ]); + expect(returnedApps.length).to.equal(0); + }); }); - }); - describe("getNextApplicationId", () => { - // biome-ignore lint/suspicious/noExplicitAny: - let contract: any; + describe("getNextApplicationId", () => { + it("Should return the next application Id", async () => { + expect(await contract.read.getNextApplicationId()).to.equal( + parseUnits("0", 0), + ); - beforeEach(async () => { - const fixture = await loadFixture(deploy); - contract = fixture.contract; - }); - - it("Should return the next application ID", async () => { - expect(await contract.read.getNextApplicationId()).to.equal( - parseUnits("0", 0), - ); + const app = { + name: "app", + account: generateRandomAddress(), + }; - const app = { - name: "app", - account: generateRandomAddress() as Address, - }; - await contract.write.createApplication([app]); - expect(await contract.read.getNextApplicationId()).to.equal( - parseUnits("1", 0), - ); + await contract.write.createApplication([app]); + expect(await contract.read.getNextApplicationId()).to.equal( + parseUnits("1", 0), + ); + }); }); }); }); diff --git a/test/OIDAccessManager.ts b/test/OIDAccessManager.ts new file mode 100644 index 0000000..11ec0c6 --- /dev/null +++ b/test/OIDAccessManager.ts @@ -0,0 +1,34 @@ +import { loadFixture } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers"; +import { expect } from "chai"; +import hre from "hardhat"; + +describe("OIDAccessManager", () => { + // 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. + async function deploy() { + // Contracts are deployed using the first signer/account by default + const [deployer] = await hre.viem.getWalletClients(); + + const contract = await hre.viem.deployContract("OIDAccessManager"); + await contract.write.initialize(); + const publicClient = await hre.viem.getPublicClient(); + const ADMIN_ROLE = await contract.read.ADMIN_ROLE(); + + return { + contract, + deployer, + publicClient, + ADMIN_ROLE, + }; + } + + describe("Deployment", () => { + it("Should set the deployer as admin", async () => { + const { contract, deployer, ADMIN_ROLE } = await loadFixture(deploy); + expect( + await contract.read.hasRole([ADMIN_ROLE, deployer.account.address]), + ).to.deep.eq([true, 0]); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 2d3ec06..62d661e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1072,6 +1072,22 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/contracts-upgradeable@npm:^5.0.2": + version: 5.0.2 + resolution: "@openzeppelin/contracts-upgradeable@npm:5.0.2" + peerDependencies: + "@openzeppelin/contracts": 5.0.2 + checksum: 10c0/0bd47a4fa0ba8084c1df9573968ff02387bc21514d846b5feb4ad42f90f3ba26bb1e40f17f03e4fa24ffbe473b9ea06c137283297884ab7d5b98d2c112904dc9 + languageName: node + linkType: hard + +"@openzeppelin/contracts@npm:^5.0.2": + version: 5.0.2 + resolution: "@openzeppelin/contracts@npm:5.0.2" + checksum: 10c0/d042661db7bb2f3a4b9ef30bba332e86ac20907d171f2ebfccdc9255cc69b62786fead8d6904b8148a8f26946bc7c15eead91b95f75db0c193a99d52e528663e + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -2225,6 +2241,8 @@ __metadata: "@nomicfoundation/hardhat-verify": "npm:^2.0.0" "@nomicfoundation/hardhat-viem": "npm:^2.0.0" "@nomiclabs/hardhat-solhint": "npm:^3.1.0" + "@openzeppelin/contracts": "npm:^5.0.2" + "@openzeppelin/contracts-upgradeable": "npm:^5.0.2" "@types/chai": "npm:^4.2.0" "@types/chai-as-promised": "npm:^7.1.6" "@types/mocha": "npm:>=9.1.0"