From 5968e84ab2c39075051a7a4638af7c543bad0de4 Mon Sep 17 00:00:00 2001 From: yawn <69970183+yawn-c111@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:48:25 +0900 Subject: [PATCH 1/5] feat: added HatsHatCreator and checked unit tests --- .../hatcreator/HatsHatCreatorModule.sol | 95 ++++++ .../hatcreator/IHatsHatCreatorModule.sol | 54 ++++ pkgs/contract/helpers/deploy/Hats.ts | 13 + pkgs/contract/test/HatsHatCreatorModule.ts | 273 ++++++++++++++++++ 4 files changed, 435 insertions(+) create mode 100644 pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol create mode 100644 pkgs/contract/contracts/hatcreator/IHatsHatCreatorModule.sol create mode 100644 pkgs/contract/test/HatsHatCreatorModule.ts diff --git a/pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol b/pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol new file mode 100644 index 0000000..912f3ec --- /dev/null +++ b/pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IHatsHatCreatorModule} from "./IHatsHatCreatorModule.sol"; +import {HatsModule} from "../hats/module/HatsModule.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract HatsHatCreatorModule is HatsModule, Ownable, IHatsHatCreatorModule { + /// @dev Mapping to track addresses with hat creation authority + mapping(address => bool) public createHatAuthorities; + + /** + * @dev Constructor to initialize the contract + * @param _version The version of the contract + * @param _tmpOwner The owner of the contract + */ + constructor( + string memory _version, + address _tmpOwner + ) HatsModule(_version) Ownable(_tmpOwner) {} + + /** + * @dev Initializes the contract, setting up the owner + * @param _initData The initialization data (encoded owner address) + */ + function _setUp(bytes calldata _initData) internal override { + address _owner = abi.decode(_initData, (address)); + _transferOwnership(_owner); + } + + /** + * @notice Checks if an address has hat creation authority + * @param authority The address to check + * @return bool Whether the address has authority + */ + function hasCreateHatAuthority(address authority) public view returns (bool) { + return createHatAuthorities[authority]; + } + + /** + * @notice Grants hat creation authority to an address + * @param authority The address to grant authority to + */ + function grantCreateHatAuthority(address authority) external onlyOwner { + require(authority != address(0), "Invalid address"); + require(!hasCreateHatAuthority(authority), "Already granted"); + + createHatAuthorities[authority] = true; + emit CreateHatAuthorityGranted(authority); + } + + /** + * @notice Revokes hat creation authority from an address + * @param authority The address to revoke authority from + */ + function revokeCreateHatAuthority(address authority) external onlyOwner { + require(hasCreateHatAuthority(authority), "Not granted"); + + createHatAuthorities[authority] = false; + emit CreateHatAuthorityRevoked(authority); + } + + /** + * @notice Creates a new hat + * @param _admin The ID of the admin (parent) hat + * @param _details The details of the hat + * @param _maxSupply The maximum supply of the hat + * @param _eligibility The address of the eligibility module + * @param _toggle The address of the toggle module + * @param _mutable Whether the hat's properties are changeable after creation + * @param _imageURI The image uri for this hat + * @return uint256 The ID of the created hat + */ + function createHat( + uint256 _admin, + string calldata _details, + uint32 _maxSupply, + address _eligibility, + address _toggle, + bool _mutable, + string calldata _imageURI + ) external returns (uint256) { + require(hasCreateHatAuthority(msg.sender), "Not authorized"); + + return HATS().createHat( + _admin, + _details, + _maxSupply, + _eligibility, + _toggle, + _mutable, + _imageURI + ); + } +} diff --git a/pkgs/contract/contracts/hatcreator/IHatsHatCreatorModule.sol b/pkgs/contract/contracts/hatcreator/IHatsHatCreatorModule.sol new file mode 100644 index 0000000..fbddaf6 --- /dev/null +++ b/pkgs/contract/contracts/hatcreator/IHatsHatCreatorModule.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +interface IHatsHatCreatorModule { + /** + * @notice Grants hat creation authority to an address + * @param authority The address to grant authority to + */ + function grantCreateHatAuthority(address authority) external; + + /** + * @notice Revokes hat creation authority from an address + * @param authority The address to revoke authority from + */ + function revokeCreateHatAuthority(address authority) external; + + /** + * @notice Creates a new hat + * @param _admin The ID of the admin (parent) hat + * @param _details The details of the hat + * @param _maxSupply The maximum supply of the hat + * @param _eligibility The address of the eligibility module + * @param _toggle The address of the toggle module + * @param _mutable Whether the hat's properties are changeable after creation + * @param _imageURI The image uri for this hat + * @return hatId The ID of the created hat + */ + function createHat( + uint256 _admin, + string calldata _details, + uint32 _maxSupply, + address _eligibility, + address _toggle, + bool _mutable, + string calldata _imageURI + ) external returns (uint256 hatId); + + /** + * @notice Checks if an address has hat creation authority + * @param authority The address to check + * @return bool Whether the address has authority + */ + function hasCreateHatAuthority(address authority) external view returns (bool); + + /** + * @notice Emitted when hat creation authority is granted + */ + event CreateHatAuthorityGranted(address indexed authority); + + /** + * @notice Emitted when hat creation authority is revoked + */ + event CreateHatAuthorityRevoked(address indexed authority); +} diff --git a/pkgs/contract/helpers/deploy/Hats.ts b/pkgs/contract/helpers/deploy/Hats.ts index bca6a5f..6a34613 100644 --- a/pkgs/contract/helpers/deploy/Hats.ts +++ b/pkgs/contract/helpers/deploy/Hats.ts @@ -15,6 +15,10 @@ export type HatsTimeFrameModule = Awaited< ReturnType >["HatsTimeFrameModule"]; +export type HatsHatCreatorModule = Awaited< + ReturnType +>["HatsHatCreatorModule"]; + export const deployHatsProtocol = async () => { const Hats = await viem.deployContract("Hats", ["test", "https://test.com"]); @@ -43,3 +47,12 @@ export const deployHatsTimeFrameModule = async (version = "0.0.0") => { return { HatsTimeFrameModule }; }; + +export const deployHatsHatCreatorModule = async (tmpOwner: Address, version = "0.0.0") => { + const HatsHatCreatorModule = await viem.deployContract("HatsHatCreatorModule", [ + version, + tmpOwner, + ]); + + return { HatsHatCreatorModule }; +}; diff --git a/pkgs/contract/test/HatsHatCreatorModule.ts b/pkgs/contract/test/HatsHatCreatorModule.ts new file mode 100644 index 0000000..2365d8d --- /dev/null +++ b/pkgs/contract/test/HatsHatCreatorModule.ts @@ -0,0 +1,273 @@ +import { expect } from "chai"; +import { viem } from "hardhat"; +import type { Address, PublicClient, WalletClient } from "viem"; +import { decodeEventLog, encodeAbiParameters } from "viem"; +import { + type Hats, + type HatsModuleFactory, + type HatsHatCreatorModule, + deployHatsModuleFactory, + deployHatsProtocol, + deployHatsHatCreatorModule, +} from "../helpers/deploy/Hats"; + +describe("HatsHatCreatorModule", () => { + let Hats: Hats; + let HatsModuleFactory: HatsModuleFactory; + let HatsHatCreatorModule_IMPL: HatsHatCreatorModule; + let HatsHatCreatorModule: HatsHatCreatorModule; + let address1: WalletClient; + let address2: WalletClient; + let address3: WalletClient; + let address1Validated: Address; + let address2Validated: Address; + let address3Validated: Address; + + let topHatId: bigint; + let hatterHatId: bigint; + let roleHatId: bigint | undefined; + + let publicClient: PublicClient; + + const validateAddress = (client: WalletClient): Address => { + if (!client.account?.address) { + throw new Error("Wallet client account address is undefined"); + } + return client.account.address; + }; + + before(async () => { + const { Hats: _Hats } = await deployHatsProtocol(); + const { HatsModuleFactory: _HatsModuleFactory } = + await deployHatsModuleFactory(_Hats.address); + + Hats = _Hats; + HatsModuleFactory = _HatsModuleFactory; + + [address1, address2, address3] = await viem.getWalletClients(); + address1Validated = validateAddress(address1); + address2Validated = validateAddress(address2); + address3Validated = validateAddress(address3); + + await Hats.write.mintTopHat([ + address1Validated, + "Description", + "https://test.com/tophat.png", + ]); + + topHatId = BigInt( + "0x0000000100000000000000000000000000000000000000000000000000000000", + ); + + const { HatsHatCreatorModule: _HatsHatCreatorModule } = + await deployHatsHatCreatorModule(address1Validated); + HatsHatCreatorModule_IMPL = _HatsHatCreatorModule; + + publicClient = await viem.getPublicClient(); + }); + + describe("deploy hat creator module", () => { + it("deploy hat creator module", async () => { + // オーナーアドレスをエンコード + const initData = encodeAbiParameters( + [{ type: 'address' }], + [address1Validated] + ); + + // HatsModuleインスタンスをデプロイ + await HatsModuleFactory.write.createHatsModule([ + HatsHatCreatorModule_IMPL.address, + topHatId, + "0x", // otherImmutableArgs + initData, // 初期化データとしてオーナーアドレスを渡す + BigInt(0), + ]); + + const moduleAddress = await HatsModuleFactory.read.getHatsModuleAddress([ + HatsHatCreatorModule_IMPL.address, + topHatId, + "0x", + BigInt(0), + ]); + + HatsHatCreatorModule = await viem.getContractAt( + "HatsHatCreatorModule", + moduleAddress, + ); + + expect( + (await HatsHatCreatorModule.read.IMPLEMENTATION()).toLowerCase(), + ).equal(HatsHatCreatorModule_IMPL.address); + + // Hatter Hatを作成 + let txHash = await Hats.write.createHat([ + topHatId, + "", + 100, + "0x0000000000000000000000000000000000004a75", + "0x0000000000000000000000000000000000004a75", + true, + "", + ]); + let receipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + }); + + let _hatterHatId; + for (const log of receipt.logs) { + const decodedLog = decodeEventLog({ + abi: Hats.abi, + data: log.data, + topics: log.topics, + }); + if (decodedLog.eventName === "HatCreated") { + _hatterHatId = decodedLog.args.id; + } + } + + if (!_hatterHatId) { + throw new Error("Hatter hat ID not found in transaction logs"); + } else { + hatterHatId = _hatterHatId + } + + // Hatter HatをHatCreatorModuleにミント + await Hats.write.mintHat([hatterHatId, HatsHatCreatorModule.address]); + }); + + it("check owner", async () => { + const owner = (await HatsHatCreatorModule.read.owner()).toLowerCase(); + expect(owner).to.equal(address1Validated.toLowerCase()); + }); + + it("check createHatAuthorities are false", async () => { + let checkCreateHatAuthority; + + checkCreateHatAuthority = await HatsHatCreatorModule.read.createHatAuthorities([address1Validated]); + expect(checkCreateHatAuthority).to.be.false; + + checkCreateHatAuthority = await HatsHatCreatorModule.read.createHatAuthorities([address2Validated]); + expect(checkCreateHatAuthority).to.be.false; + + checkCreateHatAuthority = await HatsHatCreatorModule.read.createHatAuthorities([address3Validated]); + expect(checkCreateHatAuthority).to.be.false; + }); + + it("check HatsHatCreatorModule wears Hatter Hat", async () => { + // hatterHatIdが定義されていることを確認 + if (!hatterHatId) { + throw new Error("Hatter hat ID not found"); + } + + // HatsHatCreatorModuleがHatterHatを所有しているか確認 + const isWearer = await Hats.read.isWearerOfHat([ + HatsHatCreatorModule.address, + hatterHatId + ]); + expect(isWearer).to.be.true; + }); + }); + + describe("create hat authority", () => { + it("grant create hat authority", async () => { + let hasAuthority; + hasAuthority = await HatsHatCreatorModule.read.hasCreateHatAuthority([ + address2Validated, + ]); + expect(hasAuthority).to.be.false; + + await HatsHatCreatorModule.write.grantCreateHatAuthority( + [address2Validated], + { account: address1.account } + ); + + hasAuthority = await HatsHatCreatorModule.read.hasCreateHatAuthority([ + address2Validated, + ]); + expect(hasAuthority).to.be.true; + }); + + it("revoke create hat authority", async () => { + let hasAuthority; + hasAuthority = await HatsHatCreatorModule.read.hasCreateHatAuthority([ + address2Validated, + ]); + expect(hasAuthority).to.be.true; + + await HatsHatCreatorModule.write.revokeCreateHatAuthority([address2Validated]); + + hasAuthority = await HatsHatCreatorModule.read.hasCreateHatAuthority([ + address2Validated, + ]); + expect(hasAuthority).to.be.false; + }); + }); + + describe("create hat with authority", () => { + it("create hat with authority", async () => { + // 権限を付与 + await HatsHatCreatorModule.write.grantCreateHatAuthority([address2Validated]); + + // 権限を持つアドレスからのhat作成 + const hatDetails = "Test Hat"; + const maxSupply = 10; + const eligibility = "0x0000000000000000000000000000000000004a75"; + const toggle = "0x0000000000000000000000000000000000004a75"; + const mutable = true; + const imageURI = "https://test.com/hat.png"; + + const createHatTx = await HatsHatCreatorModule.write.createHat( + [hatterHatId, hatDetails, maxSupply, eligibility, toggle, mutable, imageURI], + { account: address2.account } + ); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: createHatTx, + }); + expect(receipt.status).to.equal("success"); + }); + }); + + describe("edge cases", () => { + it("should fail when unauthorized address tries to create hat", async () => { + // 権限のないアドレスからのhat作成試行 + await expect( + HatsHatCreatorModule.write.createHat( + [ + topHatId, + "Test Hat", + 10, + "0x0000000000000000000000000000000000004a75", + "0x0000000000000000000000000000000000004a75", + true, + "https://test.com/hat.png", + ], + { account: address3.account } + ) + ).to.be.rejectedWith("Not authorized"); + }); + + it("should fail when granting authority to zero address", async () => { + // zero addressへの権限付与試行 + await expect( + HatsHatCreatorModule.write.grantCreateHatAuthority([ + "0x0000000000000000000000000000000000000000", + ]) + ).to.be.rejectedWith("Invalid address"); + }); + + it("should fail when granting authority to already authorized address", async () => { + // 既に権限を持っているアドレスへの権限付与試行 + await expect( + HatsHatCreatorModule.write.grantCreateHatAuthority([address2Validated]) + ).to.be.rejectedWith("Already granted"); + }); + + it("should fail when revoking authority from unauthorized address", async () => { + // 権限を持っていないアドレスからの剥奪試行 + await expect( + HatsHatCreatorModule.write.revokeCreateHatAuthority([address1Validated]) + ).to.be.rejectedWith("Not granted"); + }); + }); +}); From 221a942575aa9d4b548710f004cbfcb39e30ce9d Mon Sep 17 00:00:00 2001 From: yawn <69970183+yawn-c111@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:24:02 +0900 Subject: [PATCH 2/5] feat: updated BigBang for HatCreator --- pkgs/contract/contracts/bigbang/BigBang.sol | 30 +++++++++++++-- .../bigbang/mock/BigBang_Mock_v2.sol | 37 +++++++++++++++---- pkgs/contract/helpers/deploy/BigBang.ts | 2 + pkgs/contract/scripts/deploy/all.ts | 9 ++++- pkgs/contract/test/BigBang.ts | 9 +++++ 5 files changed, 76 insertions(+), 11 deletions(-) diff --git a/pkgs/contract/contracts/bigbang/BigBang.sol b/pkgs/contract/contracts/bigbang/BigBang.sol index 18c048b..9fb27bf 100644 --- a/pkgs/contract/contracts/bigbang/BigBang.sol +++ b/pkgs/contract/contracts/bigbang/BigBang.sol @@ -5,6 +5,7 @@ import { IHats } from "../hats/src/Interfaces/IHats.sol"; import { IHatsModuleFactory } from "./IHatsModuleFactory.sol"; import { ISplitsCreatorFactory } from "../splitscreator/ISplitsCreatorFactory.sol"; import { HatsTimeFrameModule } from "../timeframe/HatsTimeFrameModule.sol"; +import { HatsHatCreatorModule } from "../hatcreator/HatsHatCreatorModule.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; contract BigBang is OwnableUpgradeable { @@ -16,6 +17,8 @@ contract BigBang is OwnableUpgradeable { address public HatsTimeFrameModule_IMPL; + address public HatsHatCreatorModule_IMPL; + address public SplitsFactoryV2; address public FractionToken; @@ -34,6 +37,7 @@ contract BigBang is OwnableUpgradeable { * @param _hatsAddress Address of the hats protocol V1 contract. * @param _hatsModuleFactory Address of the hats module factory contract. * @param _hatsTimeFrameModule_IMPL Address of the hats time frame module implementation contract. + * @param _hatsHatCreatorModule_IMPL Address of the hats hat creator module implementation contract. * @param _splitsCreatorFactory Address of the splits creator factory contract. * @param _splitFactoryV2 Address of the split factory V2 contract. * @param _fractionToken Address of the fraction token contract. @@ -42,6 +46,7 @@ contract BigBang is OwnableUpgradeable { address _hatsAddress, address _hatsModuleFactory, address _hatsTimeFrameModule_IMPL, + address _hatsHatCreatorModule_IMPL, address _splitsCreatorFactory, address _splitFactoryV2, address _fractionToken @@ -50,6 +55,7 @@ contract BigBang is OwnableUpgradeable { Hats = IHats(_hatsAddress); HatsModuleFactory = IHatsModuleFactory(_hatsModuleFactory); HatsTimeFrameModule_IMPL = _hatsTimeFrameModule_IMPL; + HatsHatCreatorModule_IMPL = _hatsHatCreatorModule_IMPL; SplitsCreatorFactory = ISplitsCreatorFactory(_splitsCreatorFactory); SplitsFactoryV2 = _splitFactoryV2; FractionToken = _fractionToken; @@ -84,13 +90,22 @@ contract BigBang is OwnableUpgradeable { uint256 hatterHatId = Hats.createHat( topHatId, // _admin: The id of the Hat that will control who wears the newly created hat _hatterHatDetails, - 1, + 2, 0x0000000000000000000000000000000000004A75, 0x0000000000000000000000000000000000004A75, true, _hatterHatImageURI ); + // 3. HatsHatCreatorModuleのデプロイ + address hatsHatCreatorModule = HatsModuleFactory.createHatsModule( + HatsHatCreatorModule_IMPL, + topHatId, + "", + abi.encode(_owner), // ownerを初期化データとして渡す + 0 + ); + // 4. HatsTimeFrameModuleのデプロイ address hatsTimeFrameModule = HatsModuleFactory.createHatsModule( HatsTimeFrameModule_IMPL, @@ -103,10 +118,13 @@ contract BigBang is OwnableUpgradeable { // 5. HatsTimeFrameModuleにHatterHatをMint Hats.mintHat(hatterHatId, hatsTimeFrameModule); - // 6. TopHatIdの権限を_ownerに譲渡 + // 6. HatsHatCreatorModuleにHatterHatをMint + Hats.mintHat(hatterHatId, hatsHatCreatorModule); + + // 7. TopHatIdの権限を_ownerに譲渡 Hats.transferHat(topHatId, address(this), _owner); - // 7. SplitCreatorをFactoryからデプロイ + // 8. SplitCreatorをFactoryからデプロイ address splitCreator = SplitsCreatorFactory .createSplitCreatorDeterministic( topHatId, @@ -151,6 +169,12 @@ contract BigBang is OwnableUpgradeable { HatsTimeFrameModule_IMPL = _hatsTimeFrameModuleImpl; } + function setHatsHatCreatorModuleImpl( + address _hatsHatCreatorModuleImpl + ) external onlyOwner { + HatsHatCreatorModule_IMPL = _hatsHatCreatorModuleImpl; + } + function setSplitsFactoryV2(address _splitsFactoryV2) external onlyOwner { SplitsFactoryV2 = _splitsFactoryV2; } diff --git a/pkgs/contract/contracts/bigbang/mock/BigBang_Mock_v2.sol b/pkgs/contract/contracts/bigbang/mock/BigBang_Mock_v2.sol index 87bc40b..c0d95ad 100644 --- a/pkgs/contract/contracts/bigbang/mock/BigBang_Mock_v2.sol +++ b/pkgs/contract/contracts/bigbang/mock/BigBang_Mock_v2.sol @@ -5,11 +5,9 @@ import { IHats } from "../../hats/src/Interfaces/IHats.sol"; import { IHatsModuleFactory } from "../IHatsModuleFactory.sol"; import { ISplitsCreatorFactory } from "../../splitscreator/ISplitsCreatorFactory.sol"; import { HatsTimeFrameModule } from "../../timeframe/HatsTimeFrameModule.sol"; +import { HatsHatCreatorModule } from "../../hatcreator/HatsHatCreatorModule.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -/** - * Upgradableになっている確認するための検証用BigBangコントラクト - */ contract BigBang_Mock_v2 is OwnableUpgradeable { IHats public Hats; @@ -19,14 +17,17 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { address public HatsTimeFrameModule_IMPL; + address public HatsHatCreatorModule_IMPL; + address public SplitsFactoryV2; address public FractionToken; event Executed( + address indexed creator, address indexed owner, uint256 indexed topHatId, - uint256 indexed hatterHatId, + uint256 hatterHatId, address hatsTimeFrameModule, address splitCreator ); @@ -36,6 +37,7 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { * @param _hatsAddress Address of the hats protocol V1 contract. * @param _hatsModuleFactory Address of the hats module factory contract. * @param _hatsTimeFrameModule_IMPL Address of the hats time frame module implementation contract. + * @param _hatsHatCreatorModule_IMPL Address of the hats hat creator module implementation contract. * @param _splitsCreatorFactory Address of the splits creator factory contract. * @param _splitFactoryV2 Address of the split factory V2 contract. * @param _fractionToken Address of the fraction token contract. @@ -44,6 +46,7 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { address _hatsAddress, address _hatsModuleFactory, address _hatsTimeFrameModule_IMPL, + address _hatsHatCreatorModule_IMPL, address _splitsCreatorFactory, address _splitFactoryV2, address _fractionToken @@ -52,6 +55,7 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { Hats = IHats(_hatsAddress); HatsModuleFactory = IHatsModuleFactory(_hatsModuleFactory); HatsTimeFrameModule_IMPL = _hatsTimeFrameModule_IMPL; + HatsHatCreatorModule_IMPL = _hatsHatCreatorModule_IMPL; SplitsCreatorFactory = ISplitsCreatorFactory(_splitsCreatorFactory); SplitsFactoryV2 = _splitFactoryV2; FractionToken = _fractionToken; @@ -86,13 +90,22 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { uint256 hatterHatId = Hats.createHat( topHatId, // _admin: The id of the Hat that will control who wears the newly created hat _hatterHatDetails, - 1, + 2, 0x0000000000000000000000000000000000004A75, 0x0000000000000000000000000000000000004A75, true, _hatterHatImageURI ); + // 3. HatsHatCreatorModuleのデプロイ + address hatsHatCreatorModule = HatsModuleFactory.createHatsModule( + HatsHatCreatorModule_IMPL, + topHatId, + "", + abi.encode(_owner), // ownerを初期化データとして渡す + 0 + ); + // 4. HatsTimeFrameModuleのデプロイ address hatsTimeFrameModule = HatsModuleFactory.createHatsModule( HatsTimeFrameModule_IMPL, @@ -105,10 +118,13 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { // 5. HatsTimeFrameModuleにHatterHatをMint Hats.mintHat(hatterHatId, hatsTimeFrameModule); - // 6. TopHatIdの権限を_ownerに譲渡 + // 6. HatsHatCreatorModuleにHatterHatをMint + Hats.mintHat(hatterHatId, hatsHatCreatorModule); + + // 7. TopHatIdの権限を_ownerに譲渡 Hats.transferHat(topHatId, address(this), _owner); - // 7. SplitCreatorをFactoryからデプロイ + // 8. SplitCreatorをFactoryからデプロイ address splitCreator = SplitsCreatorFactory .createSplitCreatorDeterministic( topHatId, @@ -120,6 +136,7 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { ); emit Executed( + msg.sender, _owner, topHatId, hatterHatId, @@ -152,6 +169,12 @@ contract BigBang_Mock_v2 is OwnableUpgradeable { HatsTimeFrameModule_IMPL = _hatsTimeFrameModuleImpl; } + function setHatsHatCreatorModuleImpl( + address _hatsHatCreatorModuleImpl + ) external onlyOwner { + HatsHatCreatorModule_IMPL = _hatsHatCreatorModuleImpl; + } + function setSplitsFactoryV2(address _splitsFactoryV2) external onlyOwner { SplitsFactoryV2 = _splitsFactoryV2; } diff --git a/pkgs/contract/helpers/deploy/BigBang.ts b/pkgs/contract/helpers/deploy/BigBang.ts index 5c968d9..e3f471e 100644 --- a/pkgs/contract/helpers/deploy/BigBang.ts +++ b/pkgs/contract/helpers/deploy/BigBang.ts @@ -7,6 +7,7 @@ export const deployBigBang = async (params: { hatsContractAddress: Address; hatsModuleFacotryAddress: Address; hatsTimeFrameModule_impl: Address; + hatsHatCreatorModule_impl: Address; splitsCreatorFactoryAddress: Address; splitsFactoryV2Address: Address; fractionTokenAddress: Address; @@ -30,6 +31,7 @@ export const deployBigBang = async (params: { params.hatsContractAddress, params.hatsModuleFacotryAddress, params.hatsTimeFrameModule_impl, + params.hatsHatCreatorModule_impl, params.splitsCreatorFactoryAddress, params.splitsFactoryV2Address, params.fractionTokenAddress, diff --git a/pkgs/contract/scripts/deploy/all.ts b/pkgs/contract/scripts/deploy/all.ts index 1132637..1ac0cd9 100644 --- a/pkgs/contract/scripts/deploy/all.ts +++ b/pkgs/contract/scripts/deploy/all.ts @@ -3,7 +3,10 @@ import { network } from "hardhat"; import { type Address, zeroAddress } from "viem"; import { deployBigBang } from "../../helpers/deploy/BigBang"; import { deployFractionToken } from "../../helpers/deploy/FractionToken"; -import { deployHatsTimeFrameModule } from "../../helpers/deploy/Hats"; +import { + deployHatsTimeFrameModule, + deployHatsHatCreatorModule, +} from "../../helpers/deploy/Hats"; import { deploySplitsCreator, deploySplitsCreatorFactory, @@ -30,6 +33,9 @@ const deployAll = async () => { } = loadDeployedContractAddresses(network.name); const { HatsTimeFrameModule } = await deployHatsTimeFrameModule(); + const { HatsHatCreatorModule } = await deployHatsHatCreatorModule( + "0x0000000000000000000000000000000000000001", // zero address 以外のアドレスを仮に渡す + ); const { FractionToken } = await deployFractionToken( "", @@ -47,6 +53,7 @@ const deployAll = async () => { hatsContractAddress: Hats as Address, hatsModuleFacotryAddress: HatsModuleFactory as Address, hatsTimeFrameModule_impl: HatsTimeFrameModule.address, + hatsHatCreatorModule_impl: HatsHatCreatorModule.address, splitsCreatorFactoryAddress: SplitsCreatorFactory.address, splitsFactoryV2Address: PullSplitsFactory as Address, fractionTokenAddress: FractionToken.address, diff --git a/pkgs/contract/test/BigBang.ts b/pkgs/contract/test/BigBang.ts index 67367df..275d711 100644 --- a/pkgs/contract/test/BigBang.ts +++ b/pkgs/contract/test/BigBang.ts @@ -3,6 +3,7 @@ import { viem } from "hardhat"; import { type PublicClient, type WalletClient, + Address, decodeEventLog, zeroAddress, } from "viem"; @@ -15,9 +16,11 @@ import { type Hats, type HatsModuleFactory, type HatsTimeFrameModule, + type HatsHatCreatorModule, deployHatsModuleFactory, deployHatsProtocol, deployHatsTimeFrameModule, + deployHatsHatCreatorModule, } from "../helpers/deploy/Hats"; import { type PullSplitsFactory, @@ -35,6 +38,7 @@ describe("BigBang", () => { let Hats: Hats; let HatsModuleFactory: HatsModuleFactory; let HatsTimeFrameModule_IMPL: HatsTimeFrameModule; + let HatsHatCreatorModule_IMPL: HatsHatCreatorModule; let SplitsWarehouse: SplitsWarehouse; let PullSplitsFactory: PullSplitsFactory; let PushSplitsFactory: PushSplitsFactory; @@ -59,6 +63,10 @@ describe("BigBang", () => { await deployHatsTimeFrameModule(); HatsTimeFrameModule_IMPL = _HatsTimeFrameModule; + const { HatsHatCreatorModule: _HatsHatCreatorModule } = + await deployHatsHatCreatorModule("0x0000000000000000000000000000000000000001"); // zero address 以外のアドレスを仮に渡す + HatsHatCreatorModule_IMPL = _HatsHatCreatorModule; + const { SplitsWarehouse: _SplitsWarehouse, PullSplitsFactory: _PullSplitsFactory, @@ -93,6 +101,7 @@ describe("BigBang", () => { hatsContractAddress: Hats.address, hatsModuleFacotryAddress: HatsModuleFactory.address, hatsTimeFrameModule_impl: HatsTimeFrameModule_IMPL.address, + hatsHatCreatorModule_impl: HatsHatCreatorModule_IMPL.address, splitsCreatorFactoryAddress: SplitsCreatorFactory.address, splitsFactoryV2Address: PullSplitsFactory.address, fractionTokenAddress: FractionToken.address, From 8ef7e252e25a7e28296f2eca416f75e7df0b8a06 Mon Sep 17 00:00:00 2001 From: yawn <69970183+yawn-c111@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:44:07 +0900 Subject: [PATCH 3/5] feat: enable createHatAuthorities for the owner in HatsHatCreatorModule --- pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol | 1 + pkgs/contract/test/HatsHatCreatorModule.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol b/pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol index 912f3ec..7d401ff 100644 --- a/pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol +++ b/pkgs/contract/contracts/hatcreator/HatsHatCreatorModule.sol @@ -26,6 +26,7 @@ contract HatsHatCreatorModule is HatsModule, Ownable, IHatsHatCreatorModule { function _setUp(bytes calldata _initData) internal override { address _owner = abi.decode(_initData, (address)); _transferOwnership(_owner); + createHatAuthorities[_owner] = true; } /** diff --git a/pkgs/contract/test/HatsHatCreatorModule.ts b/pkgs/contract/test/HatsHatCreatorModule.ts index 2365d8d..484dd21 100644 --- a/pkgs/contract/test/HatsHatCreatorModule.ts +++ b/pkgs/contract/test/HatsHatCreatorModule.ts @@ -144,7 +144,7 @@ describe("HatsHatCreatorModule", () => { let checkCreateHatAuthority; checkCreateHatAuthority = await HatsHatCreatorModule.read.createHatAuthorities([address1Validated]); - expect(checkCreateHatAuthority).to.be.false; + expect(checkCreateHatAuthority).to.be.true; checkCreateHatAuthority = await HatsHatCreatorModule.read.createHatAuthorities([address2Validated]); expect(checkCreateHatAuthority).to.be.false; @@ -266,7 +266,7 @@ describe("HatsHatCreatorModule", () => { it("should fail when revoking authority from unauthorized address", async () => { // 権限を持っていないアドレスからの剥奪試行 await expect( - HatsHatCreatorModule.write.revokeCreateHatAuthority([address1Validated]) + HatsHatCreatorModule.write.revokeCreateHatAuthority([address3Validated]) ).to.be.rejectedWith("Not granted"); }); }); From bbd902d742bd69f131c72edcffd9fff954def44c Mon Sep 17 00:00:00 2001 From: yawn <69970183+yawn-c111@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:44:47 +0900 Subject: [PATCH 4/5] feat: add hatsHatCreatorModule address param to BigBang event --- pkgs/contract/contracts/bigbang/BigBang.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/contract/contracts/bigbang/BigBang.sol b/pkgs/contract/contracts/bigbang/BigBang.sol index 9fb27bf..f8ce0f9 100644 --- a/pkgs/contract/contracts/bigbang/BigBang.sol +++ b/pkgs/contract/contracts/bigbang/BigBang.sol @@ -29,6 +29,7 @@ contract BigBang is OwnableUpgradeable { uint256 indexed topHatId, uint256 hatterHatId, address hatsTimeFrameModule, + address hatsHatCreatorModule, address splitCreator ); @@ -141,6 +142,7 @@ contract BigBang is OwnableUpgradeable { topHatId, hatterHatId, hatsTimeFrameModule, + hatsHatCreatorModule, splitCreator ); From 03313db3e90bbd822fbc4585bfbfb3369ebc5d6a Mon Sep 17 00:00:00 2001 From: yawn <69970183+yawn-c111@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:45:10 +0900 Subject: [PATCH 5/5] feat: integrate HatsHatCreatorModule into IntegrationTest --- pkgs/contract/test/IntegrationTest.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pkgs/contract/test/IntegrationTest.ts b/pkgs/contract/test/IntegrationTest.ts index 8d3efeb..f6bbe5a 100644 --- a/pkgs/contract/test/IntegrationTest.ts +++ b/pkgs/contract/test/IntegrationTest.ts @@ -18,6 +18,8 @@ import { type Hats, type HatsModuleFactory, type HatsTimeFrameModule, + type HatsHatCreatorModule, + deployHatsHatCreatorModule, deployHatsModuleFactory, deployHatsProtocol, deployHatsTimeFrameModule, @@ -37,6 +39,7 @@ describe("IntegrationTest", () => { let Hats: Hats; let HatsModuleFactory: HatsModuleFactory; let HatsTimeFrameModule_IMPL: HatsTimeFrameModule; + let HatsHatCreatorModule_IMPL: HatsHatCreatorModule; let SplitsWarehouse: SplitsWarehouse; let PullSplitsFactory: PullSplitsFactory; let PushSplitsFactory: PushSplitsFactory; @@ -45,6 +48,7 @@ describe("IntegrationTest", () => { let FractionToken: FractionToken; let BigBang: BigBang; let HatsTimeFrameModuleByBigBang: HatsTimeFrameModule; + let HatsHatCreatorModuleByBigBang: HatsHatCreatorModule; let SplitsCreatorByBigBang: SplitsCreator; let DeployedPullSplit: Awaited>; @@ -76,6 +80,10 @@ describe("IntegrationTest", () => { await deployHatsTimeFrameModule(); HatsTimeFrameModule_IMPL = _HatsTimeFrameModule; + const { HatsHatCreatorModule: _HatsHatCreatorModule } = + await deployHatsHatCreatorModule("0x0000000000000000000000000000000000000001"); // zero address 以外のアドレスを仮に渡す + HatsHatCreatorModule_IMPL = _HatsHatCreatorModule; + const { SplitsWarehouse: _SplitsWarehouse, PullSplitsFactory: _PullSplitsFactory, @@ -111,6 +119,7 @@ describe("IntegrationTest", () => { hatsContractAddress: Hats.address, hatsModuleFacotryAddress: HatsModuleFactory.address, hatsTimeFrameModule_impl: HatsTimeFrameModule_IMPL.address, + hatsHatCreatorModule_impl: HatsHatCreatorModule_IMPL.address, splitsCreatorFactoryAddress: SplitsCreatorFactory.address, splitsFactoryV2Address: PullSplitsFactory.address, fractionTokenAddress: FractionToken.address, @@ -122,7 +131,7 @@ describe("IntegrationTest", () => { }); it("should execute bigbang", async () => { - SplitsCreatorFactory.write.setBigBang([BigBang.address]); + await SplitsCreatorFactory.write.setBigBang([BigBang.address]); const txHash = await BigBang.write.bigbang( [ @@ -154,6 +163,7 @@ describe("IntegrationTest", () => { const hatsTimeFrameModuleAddress = decodedLog.args.hatsTimeFrameModule; + const hatsHatCreatorModuleAddress = decodedLog.args.hatsHatCreatorModule; const splitsCreatorAddress = decodedLog.args.splitCreator; topHatId = decodedLog.args.topHatId; @@ -163,6 +173,11 @@ describe("IntegrationTest", () => { "HatsTimeFrameModule", hatsTimeFrameModuleAddress, ); + HatsHatCreatorModuleByBigBang = await viem.getContractAt( + "HatsHatCreatorModule", + hatsHatCreatorModuleAddress, + ); + SplitsCreatorByBigBang = await viem.getContractAt( "SplitsCreator", splitsCreatorAddress, @@ -186,7 +201,7 @@ describe("IntegrationTest", () => { }); it("should create hat1", async () => { - const txHash = await Hats.write.createHat([ + const txHash = await HatsHatCreatorModuleByBigBang.write.createHat([ hatterHatId, "Role Hat", 10,