diff --git a/backend/contracts/Admins.sol b/backend/contracts/Admins.sol index 00c7616..808cddd 100644 --- a/backend/contracts/Admins.sol +++ b/backend/contracts/Admins.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/proxy/Clones.sol"; import "./Albums.sol"; /// @custom:security-contact contact@adrien-v.com /** @@ -30,12 +31,15 @@ contract Admins is Ownable{ mapping(address => address) public adminsContracts; address[] public adminsAccounts; address[] private superAdminsAccounts; + + address private immutable _albumsTemplate; /** * @dev Constructor that sets the deployer as the initial admin. */ constructor(address initialOwner) Ownable(initialOwner){ adminRoles[initialOwner] = Role.SuperAdmin; + _albumsTemplate = address(new Albums()); } /** @@ -117,26 +121,17 @@ contract Admins is Ownable{ require(ensureAdminDoNotExist(newAdmin), "admin exists"); require(ensureSuperAdmin(msg.sender), "not super admin"); require(adminRoles[newAdmin] == Role.None, "role already set"); - // todo factory call to deploy the contract and get the deployment address deployment address + adminRoles[newAdmin] = Role.Admin; - bytes memory collectionBytecode = type(Albums).creationCode; - bytes32 salt = keccak256(abi.encodePacked(newAdmin, block.timestamp)); - address collectionAddress; - // assembly { - // collectionAddress := create2( - // 0, - // add(collectionBytecode, 0x20), - // mload(collectionBytecode), - // salt - // ) - // if iszero(extcodesize(collectionAddress)) { - // // revert if something gone wrong (collectionAddress doesn't contain an address) - // revert(0, 0) - // } - // } - adminsContracts[newAdmin] = collectionAddress; // change this with deployed address - adminsAccounts.push(collectionAddress); - emit Granted(msg.sender,newAdmin, Role.Admin, collectionAddress); + + address clone = Clones.clone(_albumsTemplate); + Albums(clone).initialize( + newAdmin + ); + + adminsContracts[newAdmin] = clone; + adminsAccounts.push(clone); + emit Granted(msg.sender, newAdmin, Role.Admin, clone); } function removeAdmin(address oldAdmin) external { diff --git a/backend/contracts/Albums.sol b/backend/contracts/Albums.sol index 2fb9348..470e481 100644 --- a/backend/contracts/Albums.sol +++ b/backend/contracts/Albums.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract Albums is Ownable { +contract Albums is OwnableUpgradeable { struct Album { uint16 id; @@ -42,7 +42,11 @@ contract Albums is Ownable { uint16 song_id ); - constructor(address initialOwner) Ownable(initialOwner) {} + function initialize( + address _user + ) external initializer { + _transferOwnership(_user); + } function createAlbum( uint64 maxSupply, diff --git a/backend/package.json b/backend/package.json index 268ef93..66e5950 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,6 +11,7 @@ "@nomicfoundation/hardhat-network-helpers": "^1.0.0", "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@nomicfoundation/hardhat-verify": "^2.0.0", + "@openzeppelin/contracts-upgradeable": "^5.0.2", "@typechain/ethers-v6": "^0.5.0", "@typechain/hardhat": "^9.0.0", "chai": "^4.2.0", diff --git a/backend/test/Admins.js b/backend/test/Admins.js index de3a3c8..9dfc716 100644 --- a/backend/test/Admins.js +++ b/backend/test/Admins.js @@ -5,6 +5,7 @@ const { const { ethers } = require("hardhat"); const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); const { expect } = require("chai"); +const { ZeroAddress } = require("ethers"); describe("Admins", function () { let owner; @@ -15,12 +16,10 @@ describe("Admins", function () { beforeEach(async function () { const Admins = await ethers.getContractFactory("Admins"); - + [owner, addr1, addr2, ...addrs] = await ethers.getSigners(); admins = await Admins.deploy(owner.address); //adminsAdd = admins.target; - - }); describe("Deployment", function () { @@ -30,47 +29,49 @@ describe("Admins", function () { }); describe("addAdmin", function () { - it("Should not be used without super privilegies", async function () { - await expect(admins.connect(addr1).addAdmin(addr2.address)).to.be.revertedWith("not super admin"); + await expect( + admins.connect(addr1).addAdmin(addr2.address) + ).to.be.revertedWith("not super admin"); }); it("Should not downgrade a super admin", async function () { - await expect(admins.addAdmin(owner.address)).to.be.revertedWith("admin exists"); + await expect(admins.addAdmin(owner.address)).to.be.revertedWith( + "admin exists" + ); }); it("Should fail if admin already exists", async function () { await admins.addAdmin(addr1.address); - await expect(admins.addAdmin(addr1.address)).to.be.revertedWith("admin exists"); + await expect(admins.addAdmin(addr1.address)).to.be.revertedWith( + "admin exists" + ); }); it("Should setup adminRoles and adminsContracts", async function () { await admins.addAdmin(addr1.address); - await expect( await admins.adminRoles(addr1.address)).to.equal(1); - await expect( await admins.adminsContracts(addr1.address) != 0 ); - // todo : fix with the deployed contract + expect(await admins.adminRoles(addr1.address)).to.equal(1); + expect(await admins.adminsContracts(addr1.address)).not.to.equal(ZeroAddress); }); - }); describe("removeAdmin", function () { - it("Should not be used without super privilegies", async function () { - await expect(admins.connect(addr1).removeAdmin(addr2.address)).to.be.reverted; + await expect(admins.connect(addr1).removeAdmin(addr2.address)).to.be + .reverted; }); it("Should clean adminRoles", async function () { await admins.addAdmin(addr1.address); await admins.removeAdmin(addr1.address); - await expect( await admins.adminRoles(addr1.address)).to.equal(0); + expect(await admins.adminRoles(addr1.address)).to.equal(ZeroAddress); }); - }); describe("addSuperAdmin", function () { - it("Should not be used without super privilegies", async function () { - await expect(admins.connect(addr1).addSuperAdmin(addr2.address)).to.be.reverted; + await expect(admins.connect(addr1).addSuperAdmin(addr2.address)).to.be + .reverted; }); it("Should fail if superadmin already exists", async function () { @@ -80,57 +81,50 @@ describe("Admins", function () { it("Should setup adminRoles", async function () { await admins.addSuperAdmin(addr1.address); - await expect( await admins.adminRoles(addr1.address)).to.equal(2); - // todo : fix with the deployed contract + await expect(await admins.adminRoles(addr1.address)).to.equal(2); + // todo : fix with the deployed contract }); - }); describe("removeSuperAdmin", function () { - it("Should not be used without super privilegies", async function () { - await expect(admins.connect(addr1).removeSuperAdmin(addr2.address)).to.be.reverted; + await expect(admins.connect(addr1).removeSuperAdmin(addr2.address)).to.be + .reverted; }); it("Should clean adminRoles", async function () { await admins.addSuperAdmin(addr1.address); await admins.removeSuperAdmin(addr1.address); - await expect( await admins.adminRoles(addr1.address)).to.equal(0); + await expect(await admins.adminRoles(addr1.address)).to.equal(0); }); - }); - - describe("Events", function () { - it("Should emit an event on addAdmin", async function () { await expect(admins.addAdmin(addr1.address)) - .to.emit(admins, 'Granted') - .withArgs( owner.address,addr1.address, 1, anyValue); + .to.emit(admins, "Granted") + .withArgs(owner.address, addr1.address, 1, anyValue); }); it("Should emit an event on addSuperAdmin", async function () { await expect(admins.addSuperAdmin(addr2.address)) - .to.emit(admins, 'Granted') - .withArgs(owner.address,addr2.address, 2, anyValue ); + .to.emit(admins, "Granted") + .withArgs(owner.address, addr2.address, 2, anyValue); }); + }); + // describe("Transfers", function () { + // it("Should transfer the funds to the owner", async function () { + // const { lock, unlockTime, lockedAmount, owner } = await loadFixture( + // deployOneYearLockFixture + // ); - }) - - // describe("Transfers", function () { - // it("Should transfer the funds to the owner", async function () { - // const { lock, unlockTime, lockedAmount, owner } = await loadFixture( - // deployOneYearLockFixture - // ); - - // await time.increaseTo(unlockTime); + // await time.increaseTo(unlockTime); - // await expect(lock.withdraw()).to.changeEtherBalances( - // [owner, lock], - // [lockedAmount, -lockedAmount] - // ); - // }); - // }); + // await expect(lock.withdraw()).to.changeEtherBalances( + // [owner, lock], + // [lockedAmount, -lockedAmount] + // ); + // }); + // }); }); diff --git a/backend/test/Albums.js b/backend/test/Albums.js index 4e5c6d0..4f60e11 100644 --- a/backend/test/Albums.js +++ b/backend/test/Albums.js @@ -1,7 +1,7 @@ const { loadFixture, } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); -const { ethers } = require("hardhat"); +const { ethers, deployments } = require("hardhat"); const { utils } = require("ethers"); const { expect } = require("chai"); @@ -10,7 +10,8 @@ describe("Albums test", async function () { const [owner, otherAccount1] = await ethers.getSigners(); const Albums = await ethers.getContractFactory("Albums"); - const albums = await Albums.deploy(owner.address); + const albums = await Albums.deploy(); + await albums.initialize(owner.address); return { albums, owner, otherAccount1 }; }; diff --git a/backend/yarn.lock b/backend/yarn.lock index 660da97..fea09d6 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -682,6 +682,11 @@ "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1" "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1" +"@openzeppelin/contracts-upgradeable@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.2.tgz#3e5321a2ecdd0b206064356798c21225b6ec7105" + integrity sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ== + "@openzeppelin/contracts@^5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210" @@ -1833,7 +1838,7 @@ ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" -ethers@^6.4.0, ethers@^6.7.0: +ethers@^6.11.1, ethers@^6.7.0: version "6.11.1" resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.11.1.tgz#96aae00b627c2e35f9b0a4d65c7ab658259ee6af" integrity sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==