diff --git a/pkgs/contract/.openzeppelin/sepolia.json b/pkgs/contract/.openzeppelin/sepolia.json index 3da8ea8..3f01793 100644 --- a/pkgs/contract/.openzeppelin/sepolia.json +++ b/pkgs/contract/.openzeppelin/sepolia.json @@ -1082,6 +1082,177 @@ ] } } + }, + "bc99c04f0dfb4992ed165e54e943343e226f7658165be2a79475a81a43158d28": { + "address": "0xE9087c88980D08c5C20C92792b7CfbfE8606E67A", + "txHash": "0x57b6be25d318764f5d965c683b83cb7e7ecda118c8ee28e7c9d2b5ec48dc4353", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "TOKEN_SUPPLY", + "offset": 0, + "slot": "0", + "type": "t_uint256", + "contract": "FractionToken", + "src": "contracts/fractiontoken/FractionToken.sol:10" + }, + { + "label": "tokenRecipients", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_uint256,t_array(t_address)dyn_storage)", + "contract": "FractionToken", + "src": "contracts/fractiontoken/FractionToken.sol:12" + }, + { + "label": "hatsContract", + "offset": 0, + "slot": "2", + "type": "t_contract(IHats)7761", + "contract": "FractionToken", + "src": "contracts/fractiontoken/FractionToken.sol:14" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_bool))": { + "label": "mapping(address => mapping(address => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_uint256))": { + "label": "mapping(uint256 => mapping(address => uint256))", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(ERC1155Storage)166_storage": { + "label": "struct ERC1155Upgradeable.ERC1155Storage", + "members": [ + { + "label": "_balances", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "offset": 0, + "slot": "0" + }, + { + "label": "_operatorApprovals", + "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", + "offset": 0, + "slot": "1" + }, + { + "label": "_uri", + "type": "t_string_storage", + "offset": 0, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(InitializableStorage)73_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_contract(IHats)7761": { + "label": "contract IHats", + "numberOfBytes": "20" + }, + "t_mapping(t_uint256,t_array(t_address)dyn_storage)": { + "label": "mapping(uint256 => address[])", + "numberOfBytes": "32" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.ERC1155": [ + { + "contract": "ERC1155Upgradeable", + "label": "_balances", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_uint256))", + "src": "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol:27", + "offset": 0, + "slot": "0" + }, + { + "contract": "ERC1155Upgradeable", + "label": "_operatorApprovals", + "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", + "src": "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol:29", + "offset": 0, + "slot": "1" + }, + { + "contract": "ERC1155Upgradeable", + "label": "_uri", + "type": "t_string_storage", + "src": "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol:32", + "offset": 0, + "slot": "2" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } } } } diff --git a/pkgs/contract/contracts/fractiontoken/FractionToken.sol b/pkgs/contract/contracts/fractiontoken/FractionToken.sol index 2615a38..217c750 100644 --- a/pkgs/contract/contracts/fractiontoken/FractionToken.sol +++ b/pkgs/contract/contracts/fractiontoken/FractionToken.sol @@ -3,8 +3,10 @@ pragma solidity ^0.8.24; import { ERC1155Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; import { IHats } from "../hats/src/Interfaces/IHats.sol"; +import { IFractionToken } from "./IFractionToken.sol"; +import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -contract FractionToken is ERC1155Upgradeable { +contract FractionToken is ERC1155Upgradeable, IFractionToken { uint256 public TOKEN_SUPPLY; mapping(uint256 => address[]) private tokenRecipients; @@ -21,7 +23,11 @@ contract FractionToken is ERC1155Upgradeable { TOKEN_SUPPLY = _tokenSupply; } - function mintInitialSupply(uint256 hatId, address account) public { + function mintInitialSupply( + uint256 hatId, + address account, + uint256 amount + ) public { require( _hasHatRole(account, hatId), "This account does not have the role" @@ -39,9 +45,12 @@ contract FractionToken is ERC1155Upgradeable { "This account has already received" ); - _mint(account, tokenId, TOKEN_SUPPLY, ""); + uint256 initialAmount = amount > 0 ? amount : TOKEN_SUPPLY; + _mint(account, tokenId, initialAmount, ""); tokenRecipients[tokenId].push(account); + + emit InitialMint(account, hatId, tokenId); } function mint(uint256 hatId, address account, uint256 amount) public { @@ -82,7 +91,7 @@ contract FractionToken is ERC1155Upgradeable { uint256 tokenId, uint256 amount, bytes memory data - ) public override { + ) public override(ERC1155Upgradeable, IERC1155) { super.safeTransferFrom(from, to, tokenId, amount, data); if (!_containsRecipient(tokenId, to)) { @@ -96,7 +105,7 @@ contract FractionToken is ERC1155Upgradeable { uint256[] memory tokenIds, uint256[] memory amounts, bytes memory data - ) public override { + ) public override(ERC1155Upgradeable, IERC1155) { super.safeBatchTransferFrom(from, to, tokenIds, amounts, data); for (uint256 i = 0; i < tokenIds.length; i++) { diff --git a/pkgs/contract/contracts/fractiontoken/IFractionToken.sol b/pkgs/contract/contracts/fractiontoken/IFractionToken.sol index 7f68caa..f5c38de 100644 --- a/pkgs/contract/contracts/fractiontoken/IFractionToken.sol +++ b/pkgs/contract/contracts/fractiontoken/IFractionToken.sol @@ -5,39 +5,45 @@ pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; interface IFractionToken is IERC1155 { - function mintInitialSupply( - string memory hatId, - address account, - uint256 amount - ) external; - - function burn( - address from, + event InitialMint( + address indexed wearer, + uint256 indexed hatId, + uint256 indexed tokenId + ); + + function mintInitialSupply( + uint256 hatId, + address account, + uint256 amount + ) external; + + function mint(uint256 hatId, address account, uint256 amount) external; + + function burn( + address from, address wearer, uint256 hatId, uint256 value - ) external; - - function getTokenRecipients( - uint256 tokenId - ) external view returns (address[] memory); - - function getAllTokenIds() external view returns (uint256[] memory); - - function getTokenId( - uint256 hatId, - address account - ) external view returns (uint256); - - function balanceOf( - address account, - address warer, - uint256 hatId - ) external view returns (uint256); - - function balanceOfBatch( - address[] memory accounts, - address[] memory warers, - uint256[] memory hatIds - ) external view returns (uint256[] memory); + ) external; + + function getTokenRecipients( + uint256 tokenId + ) external view returns (address[] memory); + + function getTokenId( + uint256 hatId, + address account + ) external view returns (uint256); + + function balanceOf( + address account, + address warer, + uint256 hatId + ) external view returns (uint256); + + function balanceOfBatch( + address[] memory accounts, + address[] memory warers, + uint256[] memory hatIds + ) external view returns (uint256[] memory); } diff --git a/pkgs/contract/contracts/splitscreator/SplitsCreatorFactory.sol b/pkgs/contract/contracts/splitscreator/SplitsCreatorFactory.sol index 7848d42..2ddf1b5 100644 --- a/pkgs/contract/contracts/splitscreator/SplitsCreatorFactory.sol +++ b/pkgs/contract/contracts/splitscreator/SplitsCreatorFactory.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.24; import { LibClone } from "solady/src/utils/LibClone.sol"; import { SplitsCreator } from "./SplitsCreator.sol"; import { ISplitsCreator } from "./ISplitsCreator.sol"; -import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract SplitsCreatorFactory is Initializable { +contract SplitsCreatorFactory is OwnableUpgradeable { event SplitCreatorCreated( address indexed creator, address indexed splitCreator, @@ -18,10 +18,13 @@ contract SplitsCreatorFactory is Initializable { ); address public SPLITS_CREATOR_IMPLEMENTATION; + + address public BIG_BANG; function initialize( address _splitsCreatorImplementation ) public initializer { + __Ownable_init(_msgSender()); SPLITS_CREATOR_IMPLEMENTATION = _splitsCreatorImplementation; } @@ -33,6 +36,10 @@ contract SplitsCreatorFactory is Initializable { address _fractionToken, bytes32 _salt ) external returns (address splitCreator) { + if (_msgSender() != BIG_BANG) { + revert("SplitsCreatorFactory: Only BigBang can call this function"); + } + splitCreator = LibClone.cloneDeterministic( SPLITS_CREATOR_IMPLEMENTATION, abi.encode( @@ -90,6 +97,18 @@ contract SplitsCreatorFactory is Initializable { ); } + function setImplementation( + address _implementation + ) external onlyOwner { + SPLITS_CREATOR_IMPLEMENTATION = _implementation; + } + + function setBigBang( + address _bigBang + ) external onlyOwner { + BIG_BANG = _bigBang; + } + function _getSalt( uint256 _topHatId, address _hats, diff --git a/pkgs/contract/contracts/splitscreator/mock/SplitsCreator_Mock_v2.sol b/pkgs/contract/contracts/splitscreator/mock/SplitsCreator_Mock_v2.sol new file mode 100644 index 0000000..7c061e7 --- /dev/null +++ b/pkgs/contract/contracts/splitscreator/mock/SplitsCreator_Mock_v2.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import { LibClone } from "solady/src/utils/LibClone.sol"; +import { SplitsCreator } from "../SplitsCreator.sol"; +import { ISplitsCreator } from "../ISplitsCreator.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract SplitsCreatorFactory_Mock_v2 is OwnableUpgradeable { + event SplitCreatorCreated( + address indexed creator, + address indexed splitCreator, + uint256 topHatId, + address splitFactoryV2, + address hatsTimeFrameModule, + address fractionToken + ); + + address public SPLITS_CREATOR_IMPLEMENTATION; + + address public BIG_BANG; + + function initialize( + address _splitsCreatorImplementation + ) public initializer { + __Ownable_init(_msgSender()); + SPLITS_CREATOR_IMPLEMENTATION = _splitsCreatorImplementation; + } + + function createSplitCreatorDeterministic( + uint256 _topHatId, + address _hats, + address _splitFactoryV2, + address _hatsTimeFrameModule, + address _fractionToken, + bytes32 _salt + ) external returns (address splitCreator) { + if (_msgSender() != BIG_BANG) { + revert("SplitsCreatorFactory: Only BigBang can call this function"); + } + + splitCreator = LibClone.cloneDeterministic( + SPLITS_CREATOR_IMPLEMENTATION, + abi.encode( + _hats, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken + ), + _getSalt( + _topHatId, + _hats, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken, + _salt + ) + ); + + emit SplitCreatorCreated( + msg.sender, + splitCreator, + _topHatId, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken + ); + } + + function predictDeterministicAddress( + uint256 _topHatId, + address _hats, + address _splitFactoryV2, + address _hatsTimeFrameModule, + address _fractionToken, + bytes32 _salt + ) external view returns (address) { + return + LibClone.predictDeterministicAddress( + SPLITS_CREATOR_IMPLEMENTATION, + abi.encode( + _hats, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken + ), + _getSalt( + _topHatId, + _hats, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken, + _salt + ), + address(this) + ); + } + + function setImplementation( + address _implementation + ) external onlyOwner { + SPLITS_CREATOR_IMPLEMENTATION = _implementation; + } + + function setBigBang( + address _bigBang + ) external onlyOwner { + BIG_BANG = _bigBang; + } + + function _getSalt( + uint256 _topHatId, + address _hats, + address _splitFactoryV2, + address _hatsTimeFrameModule, + address _fractionToken, + bytes32 _salt + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + _topHatId, + _hats, + _splitFactoryV2, + _hatsTimeFrameModule, + _fractionToken, + _salt + ) + ); + } + + /** + * 検証用に追加した関数 + */ + function testUpgradeFunction() external pure returns (string memory) { + return "testUpgradeFunction"; + } +} diff --git a/pkgs/contract/helpers/upgrade/fractionToken.ts b/pkgs/contract/helpers/upgrade/fractionToken.ts index 7be62db..576a376 100644 --- a/pkgs/contract/helpers/upgrade/fractionToken.ts +++ b/pkgs/contract/helpers/upgrade/fractionToken.ts @@ -14,12 +14,12 @@ export const upgradeFractionToken = async ( params?: any[] ) => { // 新しいコントラクトのファクトリーを取得 - const FractionToken_Mock_v2 = await ethers.getContractFactory(contractName); + const FractionToken = await ethers.getContractFactory(contractName); // アップグレードを実行 const _FractionToken = await upgrades.upgradeProxy( contractAddress, - FractionToken_Mock_v2 + FractionToken ); const address = _FractionToken.target; diff --git a/pkgs/contract/helpers/upgrade/splitsCreatorFactory.ts b/pkgs/contract/helpers/upgrade/splitsCreatorFactory.ts new file mode 100644 index 0000000..f3cfee3 --- /dev/null +++ b/pkgs/contract/helpers/upgrade/splitsCreatorFactory.ts @@ -0,0 +1,35 @@ +import { ethers, upgrades, viem } from "hardhat"; +import { Address } from "viem"; + +/** + * SplitsCreatorFactory Contractをアップグレードするメソッド + * @param contractAddress アップグレード対象のコントラクトアドレス + * @param contractName アップグレード後のコントラクト名 + * @param params アップグレード時に必要なパラメータ + * @returns + */ +export async function upgradeSplitsCreatorFacotry( + contractAddress: string , + contractName: string, + params: any[] +) { + // 新しいコントラクトのファクトリーを取得 + const SplitsCreator_Mock_v2 = await ethers.getContractFactory(contractName); + + // アップグレードを実行 + const _SplitsCreatorFactory = await upgrades.upgradeProxy( + contractAddress, + SplitsCreator_Mock_v2 + ); + + // 新しいアドレスを取得 + const address = _SplitsCreatorFactory.target; + + // create a new instance of the contract + const newSplitsCreatorFactory = await viem.getContractAt( + contractName, + address as Address + ); + + return newSplitsCreatorFactory; +} diff --git a/pkgs/contract/package.json b/pkgs/contract/package.json index b8aa49b..5806ef0 100644 --- a/pkgs/contract/package.json +++ b/pkgs/contract/package.json @@ -17,7 +17,7 @@ "getContractAddress": "npx hardhat getContractAddress", "registerSubdomain": "npx hardhat registerSubdomain", "createSplit": "npx hardhat run scripts/createSplit.ts", - "upgrade:FractionToken": "npx hardhat run scripts/upgrade/FractionToken.ts", + "upgrade:FractionToken": "npx hardhat run scripts/upgrade/fractionToken.ts", "upgrade:BigBang": "npx hardhat run scripts/upgrade/BigBang.ts", "bigbang": "npx hardhat bigbang", "getWoreTime": "npx hardhat getWoreTime", diff --git a/pkgs/contract/scripts/upgrade/fractionToken.ts b/pkgs/contract/scripts/upgrade/fractionToken.ts index 633dd93..2eeca61 100644 --- a/pkgs/contract/scripts/upgrade/fractionToken.ts +++ b/pkgs/contract/scripts/upgrade/fractionToken.ts @@ -22,7 +22,7 @@ const upgrade = async () => { // FractionTokenコントラクトをアップグレードする const newFractionToken = await upgradeFractionToken( FractionToken, - "FractionToken_Mock_v2" // ここにアップグレード後のFractionTokenのコントラクト名を指定する。 + "FractionToken" // ここにアップグレード後のFractionTokenのコントラクト名を指定する。 ); console.log("upgrded address:", newFractionToken.address); diff --git a/pkgs/contract/test/BigBang.ts b/pkgs/contract/test/BigBang.ts index eb254c9..326d81b 100644 --- a/pkgs/contract/test/BigBang.ts +++ b/pkgs/contract/test/BigBang.ts @@ -103,6 +103,9 @@ describe("BigBang", () => { }); it("should execute bigbang", async () => { + // SplitsCreatorFactoryにBigBangアドレスをセット + SplitsCreatorFactory.write.setBigBang([BigBang.address]); + const txHash = await BigBang.write.bigbang( [ address1.account?.address!, @@ -316,6 +319,9 @@ describe("BigBang", () => { "BigBang_Mock_v2" ); + // SplitsCreatorFactoryにBigBangアドレスをセット + SplitsCreatorFactory.write.setBigBang([newBigBang.address]); + const txHash = await newBigBang.write.bigbang( [ address1.account?.address!, diff --git a/pkgs/contract/test/FractionToken.ts b/pkgs/contract/test/FractionToken.ts index 066d263..f7fe8dd 100644 --- a/pkgs/contract/test/FractionToken.ts +++ b/pkgs/contract/test/FractionToken.ts @@ -96,11 +96,11 @@ describe("FractionToken", () => { it("should mint, transfer and burn tokens", async () => { // address1がaddress2,address3にtokenをmint await FractionToken.write.mintInitialSupply( - [hatId, address2.account?.address!], + [hatId, address2.account?.address!, 0n], { account: address1.account! } ); await FractionToken.write.mintInitialSupply( - [hatId, address3.account?.address!], + [hatId, address3.account?.address!, 0n], { account: address1.account! } ); @@ -187,7 +187,7 @@ describe("FractionToken", () => { it("should fail to mint a token", async () => { // roleのない人にtokenはmintできない await FractionToken.write - .mintInitialSupply([hatId, address4.account?.address!], { + .mintInitialSupply([hatId, address4.account?.address!, 0n], { account: address1.account!, }) .catch((error: any) => { @@ -196,7 +196,7 @@ describe("FractionToken", () => { // 権限のない人はtokenをmintできない await FractionToken.write - .mintInitialSupply([hatId, address2.account?.address!], { + .mintInitialSupply([hatId, address2.account?.address!, 0n], { account: address2.account!, }) .catch((error: any) => { @@ -207,7 +207,7 @@ describe("FractionToken", () => { // tokenは二度mintできない await FractionToken.write - .mintInitialSupply([hatId, address2.account?.address!]) + .mintInitialSupply([hatId, address2.account?.address!, 0n]) .catch((error: any) => { expect(error.message).to.include("This account has already received"); }); diff --git a/pkgs/contract/test/IntegrationTest.ts b/pkgs/contract/test/IntegrationTest.ts index e223282..daf5da2 100644 --- a/pkgs/contract/test/IntegrationTest.ts +++ b/pkgs/contract/test/IntegrationTest.ts @@ -89,7 +89,7 @@ describe("IntegrationTest", () => { const { FractionToken: _FractionToken } = await deployFractionToken( "", 10000n, - Hats.address, + Hats.address ); FractionToken = _FractionToken; @@ -122,6 +122,8 @@ describe("IntegrationTest", () => { }); it("should execute bigbang", async () => { + SplitsCreatorFactory.write.setBigBang([BigBang.address]); + const txHash = await BigBang.write.bigbang( [ deployer.account?.address!, @@ -237,10 +239,12 @@ describe("IntegrationTest", () => { await FractionToken.write.mintInitialSupply([ hat1_id, address1.account?.address!, + 0n, ]); await FractionToken.write.mintInitialSupply([ hat1_id, address2.account?.address!, + 0n, ]); // Check balance for address1 diff --git a/pkgs/contract/test/SplitsCreator.ts b/pkgs/contract/test/SplitsCreator.ts index f683ca8..a8a1fd9 100644 --- a/pkgs/contract/test/SplitsCreator.ts +++ b/pkgs/contract/test/SplitsCreator.ts @@ -33,6 +33,7 @@ import { SplitsWarehouse, } from "../helpers/deploy/Splits"; import { sqrt } from "../helpers/util/sqrt"; +import { upgradeSplitsCreatorFacotry } from "../helpers/upgrade/splitsCreatorFactory"; describe("SplitsCreator Factory", () => { let Hats: Hats; @@ -48,6 +49,8 @@ describe("SplitsCreator Factory", () => { let FractionToken: FractionToken; let address1: WalletClient; + let bigBangAddress: WalletClient; + let newImplementation: WalletClient; let topHatId: bigint; @@ -83,7 +86,8 @@ describe("SplitsCreator Factory", () => { const { SplitsCreator: _SplitsCreator } = await deploySplitsCreator(); SplitsCreator_IMPL = _SplitsCreator; - [address1] = await viem.getWalletClients(); + [address1, bigBangAddress, newImplementation] = + await viem.getWalletClients(); await Hats.write.mintTopHat([ address1.account?.address!, @@ -137,6 +141,12 @@ describe("SplitsCreator Factory", () => { ).to.be.a("string"); }); + it("should set BigBang Address", async () => { + await SplitsCreatorFactory.write.setBigBang([ + bigBangAddress.account?.address!, + ]); + }); + it("Should deploy SplitsCreator", async () => { const predictedAddress = await SplitsCreatorFactory.read.predictDeterministicAddress([ @@ -148,14 +158,17 @@ describe("SplitsCreator Factory", () => { keccak256("0x1234"), ]); - await SplitsCreatorFactory.write.createSplitCreatorDeterministic([ - topHatId, - Hats.address, - PullSplitsFactory.address, - HatsTimeFrameModule.address, - FractionToken.address, - keccak256("0x1234"), - ]); + await SplitsCreatorFactory.write.createSplitCreatorDeterministic( + [ + topHatId, + Hats.address, + PullSplitsFactory.address, + HatsTimeFrameModule.address, + FractionToken.address, + keccak256("0x1234"), + ], + { account: bigBangAddress.account } + ); SplitsCreator = await viem.getContractAt("SplitsCreator", predictedAddress); @@ -166,6 +179,32 @@ describe("SplitsCreator Factory", () => { FractionToken.address.toLowerCase() ); }); + + it("should change SplitsCreator implementation address", async () => { + await SplitsCreatorFactory.write.setImplementation([ + newImplementation.account?.address!, + ]); + expect( + ( + await SplitsCreatorFactory.read.SPLITS_CREATOR_IMPLEMENTATION() + ).toLocaleLowerCase() + ).to.equal(newImplementation.account?.address!); + }); + + it("sohuld upgrade SplitsCreatorFactory", async () => { + const newSplitsCreatorFactory = await upgradeSplitsCreatorFacotry( + SplitsCreatorFactory.address, + "SplitsCreatorFactory_Mock_v2", + [] + ); + + /** + * upgrade後にしかないメソッドを実行 + */ + expect(await newSplitsCreatorFactory.read.testUpgradeFunction()).to.equal( + "testUpgradeFunction" + ); + }); }); describe("CreateSplit", () => { @@ -184,6 +223,7 @@ describe("CreateSplit", () => { let address1: WalletClient; let address2: WalletClient; let address3: WalletClient; + let bigBangAddress: WalletClient; let topHatId: bigint; let hatterHatId: bigint; @@ -229,7 +269,8 @@ describe("CreateSplit", () => { const { SplitsCreator: _SplitsCreator } = await deploySplitsCreator(); SplitsCreator_IMPL = _SplitsCreator; - [address1, address2, address3] = await viem.getWalletClients(); + [address1, address2, address3, bigBangAddress] = + await viem.getWalletClients(); publicClient = await viem.getPublicClient(); @@ -269,15 +310,20 @@ describe("CreateSplit", () => { SplitsCreatorFactory = _SplitsCreatorFactory; + SplitsCreatorFactory.write.setBigBang([bigBangAddress.account?.address!]); + let txHash = - await SplitsCreatorFactory.write.createSplitCreatorDeterministic([ - topHatId, - Hats.address, - PullSplitsFactory.address, - HatsTimeFrameModule.address, - FractionToken.address, - keccak256("0x1234"), - ]); + await SplitsCreatorFactory.write.createSplitCreatorDeterministic( + [ + topHatId, + Hats.address, + PullSplitsFactory.address, + HatsTimeFrameModule.address, + FractionToken.address, + keccak256("0x1234"), + ], + { account: bigBangAddress.account } + ); let receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, @@ -418,14 +464,17 @@ describe("CreateSplit", () => { await FractionToken.write.mintInitialSupply([ hat1_id, address1.account?.address!, + 0n, ]); await FractionToken.write.mintInitialSupply([ hat1_id, address2.account?.address!, + 0n, ]); await FractionToken.write.mintInitialSupply([ hat2_id, address3.account?.address!, + 0n, ]); // let balance: bigint; diff --git a/pkgs/frontend/.env.example b/pkgs/frontend/.env.example index df84438..e90b033 100644 --- a/pkgs/frontend/.env.example +++ b/pkgs/frontend/.env.example @@ -2,13 +2,13 @@ VITE_CHAIN_ID=11155111 VITE_PRIVY_APP_ID= -VITE_BIGBANG_ADDRESS=0x5d7a64Cc808294C516076d371685ed4E6aDd6337 +VITE_BIGBANG_ADDRESS=0x08B4c53b98f46B14E2AD00189C2Aa3b9F3d0c8f3 VITE_HATS_ADDRESS=0x3bc1A0Ad72417f2d411118085256fC53CBdDd137 -VITE_FRACTION_TOKEN_ADDRESS=0xb8f7ca7a5b1e457b8735884419e114f90d53e1d5 +VITE_FRACTION_TOKEN_ADDRESS=0xd921517fdF141d97C289bDb9686f51A1375dCc69 -VITE_SPLITS_CREATOR_ADDRESS=0x9c3648df4bb82fdf067a9b083900a986f9b27e9a +VITE_SPLITS_CREATOR_ADDRESS=0x6b5d2e27ff74e9adf4d23aebb9efb52867823583 VITE_PIMLICO_API_KEY= @@ -16,4 +16,8 @@ VITE_PINATA_JWT= VITE_PINATA_GATEWAY= -VITE_NAMESTONE_API_KEY= \ No newline at end of file +VITE_PINATA_GATEWAY_TOKEN="M-iEBglWoUCZWJYsihe1IRrngs7HIGeIr3s5lObVw96hv7GTuCw1QrlmnNtwvuXt" + +VITE_NAMESTONE_API_KEY= + +VITE_GOLDSKY_GRAPHQL_ENDPOINT= \ No newline at end of file diff --git a/pkgs/frontend/abi/bigbang.ts b/pkgs/frontend/abi/bigbang.ts index 96fde08..b20e511 100644 --- a/pkgs/frontend/abi/bigbang.ts +++ b/pkgs/frontend/abi/bigbang.ts @@ -1,44 +1,91 @@ export const BIGBANG_ABI = [ + { + inputs: [], + name: "InvalidInitialization", + type: "error", + }, + { + inputs: [], + name: "NotInitializing", + type: "error", + }, { inputs: [ { internalType: "address", - name: "_trustedForwarder", + name: "owner", type: "address", }, + ], + name: "OwnableInvalidOwner", + type: "error", + }, + { + inputs: [ { internalType: "address", - name: "_hatsAddress", + name: "account", type: "address", }, + ], + name: "OwnableUnauthorizedAccount", + type: "error", + }, + { + anonymous: false, + inputs: [ { + indexed: true, internalType: "address", - name: "_hatsModuleFactory", + name: "creator", type: "address", }, { + indexed: true, internalType: "address", - name: "_hatsTimeFrameModule_IMPL", + name: "owner", type: "address", }, { - internalType: "address", - name: "_splitsCreatorFactory", - type: "address", + indexed: true, + internalType: "uint256", + name: "topHatId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "hatterHatId", + type: "uint256", }, { + indexed: false, internalType: "address", - name: "_splitFactoryV2", + name: "hatsTimeFrameModule", type: "address", }, { + indexed: false, internalType: "address", - name: "_fractionToken", + name: "splitCreator", type: "address", }, ], - stateMutability: "nonpayable", - type: "constructor", + name: "Executed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint64", + name: "version", + type: "uint64", + }, + ], + name: "Initialized", + type: "event", }, { anonymous: false, @@ -46,30 +93,31 @@ export const BIGBANG_ABI = [ { indexed: true, internalType: "address", - name: "owner", + name: "previousOwner", type: "address", }, { indexed: true, - internalType: "uint256", - name: "topHatId", - type: "uint256", - }, - { - indexed: false, internalType: "address", - name: "hatsTimeFrameModule", + name: "newOwner", type: "address", }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + inputs: [], + name: "FractionToken", + outputs: [ { - indexed: false, internalType: "address", - name: "splitCreator", + name: "", type: "address", }, ], - name: "Executed", - type: "event", + stateMutability: "view", + type: "function", }, { inputs: [], @@ -123,6 +171,19 @@ export const BIGBANG_ABI = [ stateMutability: "view", type: "function", }, + { + inputs: [], + name: "SplitsFactoryV2", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, { inputs: [ { @@ -150,11 +211,6 @@ export const BIGBANG_ABI = [ name: "_hatterHatImageURI", type: "string", }, - { - internalType: "address", - name: "_trustedForwarder", - type: "address", - }, ], name: "bigbang", outputs: [ @@ -167,9 +223,47 @@ export const BIGBANG_ABI = [ stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { + internalType: "address", + name: "_hatsAddress", + type: "address", + }, + { + internalType: "address", + name: "_hatsModuleFactory", + type: "address", + }, + { + internalType: "address", + name: "_hatsTimeFrameModule_IMPL", + type: "address", + }, + { + internalType: "address", + name: "_splitsCreatorFactory", + type: "address", + }, + { + internalType: "address", + name: "_splitFactoryV2", + type: "address", + }, + { + internalType: "address", + name: "_fractionToken", + type: "address", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [], - name: "fractionToken", + name: "owner", outputs: [ { internalType: "address", @@ -180,49 +274,102 @@ export const BIGBANG_ABI = [ stateMutability: "view", type: "function", }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { internalType: "address", - name: "forwarder", + name: "_fractionToken", type: "address", }, ], - name: "isTrustedForwarder", - outputs: [ + name: "setFractionToken", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ { - internalType: "bool", - name: "", - type: "bool", + internalType: "address", + name: "_hats", + type: "address", }, ], - stateMutability: "view", + name: "setHats", + outputs: [], + stateMutability: "nonpayable", type: "function", }, { - inputs: [], - name: "splitFactoryV2", - outputs: [ + inputs: [ { internalType: "address", - name: "", + name: "_hatsModuleFactory", type: "address", }, ], - stateMutability: "view", + name: "setHatsModuleFactory", + outputs: [], + stateMutability: "nonpayable", type: "function", }, { - inputs: [], - name: "trustedForwarder", - outputs: [ + inputs: [ { internalType: "address", - name: "", + name: "_hatsTimeFrameModuleImpl", type: "address", }, ], - stateMutability: "view", + name: "setHatsTimeFrameModuleImpl", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_splitsCreatorFactory", + type: "address", + }, + ], + name: "setSplitsCreatorFactory", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_splitsFactoryV2", + type: "address", + }, + ], + name: "setSplitsFactoryV2", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", type: "function", }, ] as const; diff --git a/pkgs/frontend/abi/hatsTimeFrameModule.ts b/pkgs/frontend/abi/hatsTimeFrameModule.ts index ef99157..12cc5b1 100644 --- a/pkgs/frontend/abi/hatsTimeFrameModule.ts +++ b/pkgs/frontend/abi/hatsTimeFrameModule.ts @@ -1,11 +1,6 @@ export const HATS_TIME_FRAME_MODULE_ABI = [ { inputs: [ - { - internalType: "address", - name: "_trustedForwarder", - type: "address", - }, { internalType: "string", name: "_version", @@ -64,6 +59,48 @@ export const HATS_TIME_FRAME_MODULE_ABI = [ stateMutability: "pure", type: "function", }, + { + inputs: [ + { + internalType: "uint256", + name: "hatId", + type: "uint256", + }, + { + internalType: "address", + name: "wearer", + type: "address", + }, + ], + name: "deactivate", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "deactivatedTime", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, { inputs: [ { @@ -127,13 +164,18 @@ export const HATS_TIME_FRAME_MODULE_ABI = [ }, { inputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, { internalType: "address", - name: "forwarder", + name: "", type: "address", }, ], - name: "isTrustedForwarder", + name: "isActive", outputs: [ { internalType: "bool", @@ -156,12 +198,35 @@ export const HATS_TIME_FRAME_MODULE_ABI = [ name: "wearer", type: "address", }, + { + internalType: "uint256", + name: "time", + type: "uint256", + }, ], name: "mintHat", outputs: [], stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { + internalType: "uint256", + name: "hatId", + type: "uint256", + }, + { + internalType: "address", + name: "wearer", + type: "address", + }, + ], + name: "reactivate", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { @@ -176,15 +241,26 @@ export const HATS_TIME_FRAME_MODULE_ABI = [ type: "function", }, { - inputs: [], - name: "trustedForwarder", - outputs: [ + inputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, { internalType: "address", name: "", type: "address", }, ], + name: "totalActiveTime", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], stateMutability: "view", type: "function", }, @@ -214,4 +290,28 @@ export const HATS_TIME_FRAME_MODULE_ABI = [ stateMutability: "view", type: "function", }, + { + inputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "woreTime", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, ] as const; diff --git a/pkgs/frontend/app/components/BasicRole.tsx b/pkgs/frontend/app/components/BasicRole.tsx new file mode 100644 index 0000000..7dfd4c9 --- /dev/null +++ b/pkgs/frontend/app/components/BasicRole.tsx @@ -0,0 +1,19 @@ +import { Box, Image, Text } from "@chakra-ui/react"; +import { FC } from "react"; +import { HatsDetailSchama } from "types/hats"; + +interface BasicRoleProps { + detail?: HatsDetailSchama; + imageUri?: string; +} + +export const BasicRole: FC = (params?) => { + const { detail, imageUri } = params!; + + return ( + + {detail?.data.name} + + + ); +}; diff --git a/pkgs/frontend/app/components/Header.tsx b/pkgs/frontend/app/components/Header.tsx index c1b99a5..4d4da27 100644 --- a/pkgs/frontend/app/components/Header.tsx +++ b/pkgs/frontend/app/components/Header.tsx @@ -11,7 +11,7 @@ import { usePrivy, useWallets } from "@privy-io/react-auth"; import CommonButton from "./common/CommonButton"; const NO_HEADER_PATHS: string[] = ["/login", "/signup"]; // 適宜ヘッダーが不要なページのパスを追加 -const WORKSPACES_PATHS: string[] = ["/workspaces"]; // 適宜ワークスペースが未選択な状態のページのパスを追加 +const WORKSPACES_PATHS: string[] = ["/workspace", "/workspace/new"]; // 適宜ワークスペースが未選択な状態のページのパスを追加 const HEADER_SIZE: number = 12; // 偶数のnumberだとアイコンが対応しているため望ましい const headerTextStyle = { @@ -38,7 +38,7 @@ export const Header = () => { // ToDo: ページのパスや hooks で柔軟にロジックを実装する(切り替えてテストできます) const isWalletConnected = true; const isUserTobanEnsFound = true; - const isWorkspaceSelected = true; + const isWorkspaceSelected = false; // ToDo: ユーザーやワークスペースごとの各種データを取得するロジックを実装する const workspaceName: string | undefined = "Workspace Name"; diff --git a/pkgs/frontend/app/components/PageHeader.tsx b/pkgs/frontend/app/components/PageHeader.tsx new file mode 100644 index 0000000..f389e7d --- /dev/null +++ b/pkgs/frontend/app/components/PageHeader.tsx @@ -0,0 +1,37 @@ +import { HStack, IconButton, Text } from "@chakra-ui/react"; +import { useNavigate } from "@remix-run/react"; +import { ReactNode, useCallback } from "react"; +import { FaChevronLeft } from "react-icons/fa6"; + +interface Props { + title: string | ReactNode; + backLink?: string | (() => void); +} + +export const PageHeader: React.FC = ({ title, backLink }) => { + const navigate = useNavigate(); + + const handleBack = useCallback(() => { + if (backLink && typeof backLink === "string") { + navigate(backLink); + } else if (backLink && typeof backLink === "function") { + backLink(); + } else { + navigate(-1); + } + }, [backLink]); + + return ( + + + + + {title} + + ); +}; diff --git a/pkgs/frontend/app/components/color-mode-toggle.tsx b/pkgs/frontend/app/components/color-mode-toggle.tsx deleted file mode 100644 index 36d9723..0000000 --- a/pkgs/frontend/app/components/color-mode-toggle.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { IconButton } from "@chakra-ui/react"; -import { Moon, Sun } from "lucide-react"; -import { useTheme } from "next-themes"; - -export function ColorModeToggle() { - const { theme, setTheme } = useTheme(); - const toggleColorMode = () => { - setTheme(theme === "light" ? "dark" : "light"); - }; - return ( - - {theme === "light" ? : } - - ); -} diff --git a/pkgs/frontend/app/components/common/CommonDialog.tsx b/pkgs/frontend/app/components/common/CommonDialog.tsx index 4e31b63..444a4b0 100644 --- a/pkgs/frontend/app/components/common/CommonDialog.tsx +++ b/pkgs/frontend/app/components/common/CommonDialog.tsx @@ -16,7 +16,7 @@ export const CommonDialog = ({ return ( {dialogTriggerReactNode} - {children} + {children} ); }; diff --git a/pkgs/frontend/app/components/common/CommonIcon.tsx b/pkgs/frontend/app/components/common/CommonIcon.tsx index ed32b50..bf6e4e0 100644 --- a/pkgs/frontend/app/components/common/CommonIcon.tsx +++ b/pkgs/frontend/app/components/common/CommonIcon.tsx @@ -3,7 +3,8 @@ import { Box, Image } from "@chakra-ui/react"; interface CommonIconProps { imageUrl: string | undefined; - size: number | "full"; + size: number | `${number}px` | "full"; + borderRadius?: string; fallbackIconComponent?: ReactNode; } @@ -11,6 +12,7 @@ export const CommonIcon = ({ size, imageUrl, fallbackIconComponent, + borderRadius, }: CommonIconProps) => { const [showFallbackIcon, setShowFallbackIcon] = useState(!imageUrl); @@ -26,7 +28,7 @@ export const CommonIcon = ({ alignItems="center" justifyContent="center" my="auto" - borderRadius="full" + borderRadius={borderRadius} flexShrink={0} overflow="hidden" > @@ -36,7 +38,7 @@ export const CommonIcon = ({ width="100%" height="100%" objectFit="cover" - borderRadius="full" + borderRadius={borderRadius} onError={() => setShowFallbackIcon(true)} /> ) : ( diff --git a/pkgs/frontend/app/components/common/CommonInput.tsx b/pkgs/frontend/app/components/common/CommonInput.tsx index b052289..a92e650 100644 --- a/pkgs/frontend/app/components/common/CommonInput.tsx +++ b/pkgs/frontend/app/components/common/CommonInput.tsx @@ -1,22 +1,29 @@ import { Input, InputProps } from "@chakra-ui/react"; +import { FC } from "react"; interface CommonInputProps extends Omit { + minHeight?: string; value: string | number; onChange: (event: React.ChangeEvent) => void; } -export const CommonInput = ({ +export const CommonInput: FC = ({ + minHeight, value, placeholder, onChange, + ...props }: CommonInputProps) => { return ( ); }; diff --git a/pkgs/frontend/app/components/common/CommonTextarea.tsx b/pkgs/frontend/app/components/common/CommonTextarea.tsx index 6691c6c..ac9cba5 100644 --- a/pkgs/frontend/app/components/common/CommonTextarea.tsx +++ b/pkgs/frontend/app/components/common/CommonTextarea.tsx @@ -1,10 +1,27 @@ import { Textarea, TextareaProps } from "@chakra-ui/react"; interface CommonTextAreaProps extends Omit { + minHeight?: string; value: string; onChange: (event: React.ChangeEvent) => void; } -export const CommonTextArea = ({ value, onChange }: CommonTextAreaProps) => { - return