diff --git a/pkgs/contract/contracts/fractiontoken/FractionToken.sol b/pkgs/contract/contracts/fractiontoken/FractionToken.sol index b81bcaf..84cd46c 100644 --- a/pkgs/contract/contracts/fractiontoken/FractionToken.sol +++ b/pkgs/contract/contracts/fractiontoken/FractionToken.sol @@ -24,12 +24,26 @@ contract FractionToken is ERC1155Upgradeable, ERC2771ContextUpgradeable { TOKEN_SUPPLY = _tokenSupply; } - function mint(uint256 hatId, address account) public { - require(_hasHatRole(account, hatId), "not authorized"); + function mintInitialSupply( + uint256 hatId, + address account + ) public { + require( + _hasHatRole(account, hatId), + "This account does not have the role" + ); + + require( + _hasHatAuthority(hatId), + "This msg.sender does not have the authority" + ); uint256 tokenId = getTokenId(hatId, account); - require(!_containsRecipient(tokenId, account), "already received"); + require( + !_containsRecipient(tokenId, account), + "This account has already received" + ); _mint(account, tokenId, TOKEN_SUPPLY, ""); @@ -38,6 +52,26 @@ contract FractionToken is ERC1155Upgradeable, ERC2771ContextUpgradeable { } } + function mint( + uint256 hatId, + address account, + uint256 amount + ) public { + uint256 tokenId = getTokenId(hatId, account); + + require( + tokenRecipients[tokenId].length > 0, + "This account has not received the initial supply" + ); + + require( + _msgSender() == tokenRecipients[tokenId][0], + "Only the first recipient can additionally mint" + ); + + _mint(account, tokenId, amount, ""); + } + function burn( address from, address wearer, @@ -47,8 +81,8 @@ contract FractionToken is ERC1155Upgradeable, ERC2771ContextUpgradeable { uint256 tokenId = getTokenId(hatId, wearer); require( - _msgSender() == from || _containsRecipient(tokenId, _msgSender()), - "not authorized" + _msgSender() == from || _hasHatAuthority(hatId), + "Not authorized" ); _burn(from, tokenId, value); @@ -118,6 +152,20 @@ contract FractionToken is ERC1155Upgradeable, ERC2771ContextUpgradeable { return balance > 0; } + function _hasHatAuthority( + uint256 hatId + ) private view returns (bool) { + uint32 hatLevel = hatsContract.getHatLevel(hatId); + + uint256 parentHatId = hatsContract.getAdminAtLevel(hatId, hatLevel - 1); + if (_hasHatRole(_msgSender(), parentHatId)) return true; + + uint256 topHatId = hatsContract.getAdminAtLevel(hatId, 0); + if (_hasHatRole(_msgSender(), topHatId)) return true; + + return false; + } + function balanceOf( address account, address wearer, diff --git a/pkgs/contract/contracts/fractiontoken/IFractionToken.sol b/pkgs/contract/contracts/fractiontoken/IFractionToken.sol index d430b29..7f68caa 100644 --- a/pkgs/contract/contracts/fractiontoken/IFractionToken.sol +++ b/pkgs/contract/contracts/fractiontoken/IFractionToken.sol @@ -5,7 +5,11 @@ pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; interface IFractionToken is IERC1155 { - function mint(string memory hatId, address account) external; + function mintInitialSupply( + string memory hatId, + address account, + uint256 amount + ) external; function burn( address from, diff --git a/pkgs/contract/contracts/fractiontoken/mock/FractionToken_Mock_v2.sol b/pkgs/contract/contracts/fractiontoken/mock/FractionToken_Mock_v2.sol index 5cfa7e0..350b762 100644 --- a/pkgs/contract/contracts/fractiontoken/mock/FractionToken_Mock_v2.sol +++ b/pkgs/contract/contracts/fractiontoken/mock/FractionToken_Mock_v2.sol @@ -8,30 +8,44 @@ import "./../../ERC2771ContextUpgradeable.sol"; contract FractionToken_Mock_v2 is ERC1155Upgradeable, ERC2771ContextUpgradeable { -uint256 public TOKEN_SUPPLY; + uint256 public TOKEN_SUPPLY; mapping(uint256 => address[]) private tokenRecipients; - IHats public hatsContract; + IHats private hatsContract; function initialize( string memory _uri, uint256 _tokenSupply, address _hatsAddress, address _trustedForwarderAddress - ) initializer public { + ) public initializer { __ERC1155_init(_uri); __ERC2771Context_init(address(_trustedForwarderAddress)); hatsContract = IHats(_hatsAddress); TOKEN_SUPPLY = _tokenSupply; } - function mint(uint256 hatId, address account) public { - require(_hasHatRole(account, hatId), "not authorized"); + function mintInitialSupply( + uint256 hatId, + address account + ) public { + require( + _hasHatRole(account, hatId), + "This account does not have the role" + ); + + require( + _hasHatAuthority(hatId), + "This msg.sender does not have the authority" + ); uint256 tokenId = getTokenId(hatId, account); - require(!_containsRecipient(tokenId, account), "already received"); + require( + !_containsRecipient(tokenId, account), + "This account has already received" + ); _mint(account, tokenId, TOKEN_SUPPLY, ""); @@ -40,6 +54,26 @@ uint256 public TOKEN_SUPPLY; } } + function mint( + uint256 hatId, + address account, + uint256 amount + ) public { + uint256 tokenId = getTokenId(hatId, account); + + require( + tokenRecipients[tokenId].length > 0, + "This account has not received the initial supply" + ); + + require( + _msgSender() == tokenRecipients[tokenId][0], + "Only the first recipient can additionally mint" + ); + + _mint(account, tokenId, amount, ""); + } + function burn( address from, address wearer, @@ -48,7 +82,10 @@ uint256 public TOKEN_SUPPLY; ) public { uint256 tokenId = getTokenId(hatId, wearer); - require(_msgSender() == from || _containsRecipient(tokenId, _msgSender()), "not authorized"); + require( + _msgSender() == from || _hasHatAuthority(hatId), + "Not authorized" + ); _burn(from, tokenId, value); } @@ -117,6 +154,20 @@ uint256 public TOKEN_SUPPLY; return balance > 0; } + function _hasHatAuthority( + uint256 hatId + ) private view returns (bool) { + uint32 hatLevel = hatsContract.getHatLevel(hatId); + + uint256 parentHatId = hatsContract.getAdminAtLevel(hatId, hatLevel - 1); + if (_hasHatRole(_msgSender(), parentHatId)) return true; + + uint256 topHatId = hatsContract.getAdminAtLevel(hatId, 0); + if (_hasHatRole(_msgSender(), topHatId)) return true; + + return false; + } + function balanceOf( address account, address wearer, @@ -124,7 +175,9 @@ uint256 public TOKEN_SUPPLY; ) public view returns (uint256) { uint256 tokenId = getTokenId(hatId, wearer); - if (_hasHatRole(account, hatId) && !_containsRecipient(tokenId, account)) { + if ( + _hasHatRole(account, hatId) && !_containsRecipient(tokenId, account) + ) { return TOKEN_SUPPLY; } @@ -181,9 +234,9 @@ uint256 public TOKEN_SUPPLY; } /** - * 検証用に追加した関数 - */ - function testUpgradeFunction() external pure returns (string memory) { - return "testUpgradeFunction"; - } + * 検証用に追加した関数 + */ + function testUpgradeFunction() external pure returns (string memory) { + return "testUpgradeFunction"; + } } diff --git a/pkgs/contract/gas-report.txt b/pkgs/contract/gas-report.txt new file mode 100644 index 0000000..995c10d --- /dev/null +++ b/pkgs/contract/gas-report.txt @@ -0,0 +1,31 @@ +·------------------------|----------------------------|-------------|-----------------------------· +| Solc version: 0.8.24 · Optimizer enabled: false · Runs: 200 · Block limit: 30000000 gas │ +·························|····························|·············|······························ +| Methods │ +··············|··········|··············|·············|·············|···············|·············· +| Contract · Method · Min · Max · Avg · # calls · usd (avg) │ +··············|··········|··············|·············|·············|···············|·············· +| Deployments · · % of limit · │ +·························|··············|·············|·············|···············|·············· +| BigBang · - · - · 1248128 · 4.2 % · - │ +·························|··············|·············|·············|···············|·············· +| FractionToken · - · - · 3131808 · 10.4 % · - │ +·························|··············|·············|·············|···············|·············· +| Hats · - · - · 7032431 · 23.4 % · - │ +·························|··············|·············|·············|···············|·············· +| HatsModule · - · - · 754132 · 2.5 % · - │ +·························|··············|·············|·············|···············|·············· +| HatsModuleFactory · - · - · 1101122 · 3.7 % · - │ +·························|··············|·············|·············|···············|·············· +| HatsTimeFrameModule · - · - · 1287099 · 4.3 % · - │ +·························|··············|·············|·············|···············|·············· +| PullSplitFactory · 4535815 · 4535827 · 4535825 · 15.1 % · - │ +·························|··············|·············|·············|···············|·············· +| PushSplitFactory · 4483101 · 4483113 · 4483111 · 14.9 % · - │ +·························|··············|·············|·············|···············|·············· +| SplitsCreator · - · - · 1487532 · 5 % · - │ +·························|··············|·············|·············|···············|·············· +| SplitsCreatorFactory · - · - · 526836 · 1.8 % · - │ +·························|··············|·············|·············|···············|·············· +| SplitsWarehouse · - · - · 3934655 · 13.1 % · - │ +·------------------------|--------------|-------------|-------------|---------------|-------------· diff --git a/pkgs/contract/test/Burn.ts b/pkgs/contract/test/Burn.ts deleted file mode 100644 index f6a76cc..0000000 --- a/pkgs/contract/test/Burn.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { expect } from "chai"; -import { viem } from "hardhat"; -import { decodeEventLog, PublicClient, WalletClient, zeroAddress } from "viem"; -import { - deployFractionToken, - FractionToken, -} from "../helpers/deploy/FractionToken"; -import { deployHatsProtocol, Hats } from "../helpers/deploy/Hats"; - -describe("Burn", () => { - let Hats: Hats; - let FractionToken: FractionToken; - - let address1: WalletClient; - let address2: WalletClient; - let address3: WalletClient; - - let hatId: bigint; - - let publicClient: PublicClient; - - before(async () => { - const { Hats: _Hats } = await deployHatsProtocol(); - Hats = _Hats; - - const { FractionToken: _FractionToken } = await deployFractionToken( - "", - 10000n, - Hats.address, - zeroAddress - ); - FractionToken = _FractionToken; - - [address1, address2, address3] = await viem.getWalletClients(); - - publicClient = await viem.getPublicClient(); - - await Hats.write.mintTopHat([ - address1.account?.address!, - "Description", - "https://test.com/tophat.png", - ]); - - let txHash = await Hats.write.createHat([ - BigInt( - "0x0000000100000000000000000000000000000000000000000000000000000000" - ), - "role1", - 10, - "0x0000000000000000000000000000000000004a75", - "0x0000000000000000000000000000000000004a75", - true, - "https://test.com/hat_image.png", - ]); - - let receipt = await publicClient.waitForTransactionReceipt({ - hash: txHash, - }); - - for (const log of receipt.logs) { - try { - const decodedLog = decodeEventLog({ - abi: Hats.abi, - data: log.data, - topics: log.topics, - }); - if (decodedLog.eventName == "HatCreated") hatId = decodedLog.args.id; - } catch (error) {} - } - - // address1とaddress2にHatをmint - await Hats.write.mintHat([hatId, address1.account?.address!]); - await Hats.write.mintHat([hatId, address2.account?.address!]); - - // address1とaddress2にFractionTokenをmint - await FractionToken.write.mint([hatId, address1.account?.address!]); - await FractionToken.write.mint([hatId, address2.account?.address!]); - - const tokenId = await FractionToken.read.getTokenId([ - hatId, - address2.account?.address!, - ]); - - // address2のtokenの半分をaddress3に移動 - await FractionToken.write.safeTransferFrom( - [ - address2.account?.address!, - address3.account?.address!, - tokenId, - 5000n, - "0x", - ], - { - account: address2.account!, - } - ); - }); - - it("should burn tokens", async () => { - // address1のtokenを自身で半分burnする - await FractionToken.write.burn( - [address1.account?.address!, address1.account?.address!, hatId, 5000n], - { - account: address1.account!, - } - ); - - // address3のtokenをaddress2によってすべてburnする - await FractionToken.write.burn( - [address3.account?.address!, address2.account?.address!, hatId, 5000n], - { - account: address2.account!, - } - ); - - // address3のtokenをaddress1によってすべてburnするとRevertする - await expect( - FractionToken.write.burn( - [address3.account?.address!, address2.account?.address!, hatId, 5000n], - { - account: address1.account!, - } - ) - ).to.be.rejectedWith("not authorized"); - - let balance: bigint; - - // address1のbalance - balance = await FractionToken.read.balanceOf([ - address1.account?.address!, - address1.account?.address!, - hatId, - ]); - expect(balance).to.equal(5000n); - - // address2のbalance - balance = await FractionToken.read.balanceOf([ - address2.account?.address!, - address2.account?.address!, - hatId, - ]); - expect(balance).to.equal(5000n); - - // address3のbalance - balance = await FractionToken.read.balanceOf([ - address3.account?.address!, - address2.account?.address!, - hatId, - ]); - expect(balance).to.equal(0n); - }); -}); diff --git a/pkgs/contract/test/FractionToken.ts b/pkgs/contract/test/FractionToken.ts index f2498ba..7ae5197 100644 --- a/pkgs/contract/test/FractionToken.ts +++ b/pkgs/contract/test/FractionToken.ts @@ -18,6 +18,7 @@ describe("FractionToken", () => { let address4: WalletClient; let hatId: bigint; + let topHatId: bigint; let publicClient: PublicClient; @@ -39,13 +40,28 @@ describe("FractionToken", () => { publicClient = await viem.getPublicClient(); - await Hats.write.mintTopHat([ + let tx1 = await Hats.write.mintTopHat([ address1.account?.address!, "Description", "https://test.com/tophat.png", ]); - let txHash = await Hats.write.createHat([ + let receipt1 = await publicClient.waitForTransactionReceipt({ + hash: tx1, + }); + + for (const log of receipt1.logs) { + try { + const decodedLog = decodeEventLog({ + abi: Hats.abi, + data: log.data, + topics: log.topics, + }); + if (decodedLog.eventName === "HatCreated") topHatId = decodedLog.args.id; + } catch (error) {} + } + + let tx2 = await Hats.write.createHat([ BigInt( "0x0000000100000000000000000000000000000000000000000000000000000000" ), @@ -57,11 +73,11 @@ describe("FractionToken", () => { "https://test.com/hat_image.png", ]); - let receipt = await publicClient.waitForTransactionReceipt({ - hash: txHash, + let receipt2 = await publicClient.waitForTransactionReceipt({ + hash: tx2, }); - for (const log of receipt.logs) { + for (const log of receipt2.logs) { try { const decodedLog = decodeEventLog({ abi: Hats.abi, @@ -72,32 +88,25 @@ describe("FractionToken", () => { } catch (error) {} } - // address1,address2,address4にHatをmint - await Hats.write.mintHat([hatId, address1.account?.address!]); + // address2,address3にHatをmint await Hats.write.mintHat([hatId, address2.account?.address!]); + await Hats.write.mintHat([hatId, address3.account?.address!]); }); it("should mint, transfer and burn tokens", async () => { - // address1,address2にtokenをmint - await FractionToken.write.mint([hatId, address1.account?.address!]); - await FractionToken.write.mint([hatId, address2.account?.address!]); - - const tokenId = await FractionToken.read.getTokenId([ - hatId, - address2.account?.address!, - ]); + // address1がaddress2,address3にtokenをmint + await FractionToken.write.mintInitialSupply( + [hatId, address2.account?.address!], + { account: address1.account! } + ); + await FractionToken.write.mintInitialSupply( + [hatId, address3.account?.address!], + { account: address1.account! } + ); // transfer と burn前の残高情報を取得する let balance: bigint; - // 処理前のaddress1のbalance - balance = await FractionToken.read.balanceOf([ - address1.account?.address!, - address1.account?.address!, - hatId, - ]); - expect(balance).to.equal(10000n); - // 処理前のaddress2のbalance balance = await FractionToken.read.balanceOf([ address2.account?.address!, @@ -109,93 +118,141 @@ describe("FractionToken", () => { // 処理前のaddress3のbalance balance = await FractionToken.read.balanceOf([ address3.account?.address!, + address3.account?.address!, + hatId, + ]); + expect(balance).to.equal(10000n); + + // 処理前のaddress4のbalance + balance = await FractionToken.read.balanceOf([ + address4.account?.address!, address2.account?.address!, hatId, ]); expect(balance).to.equal(0n); - // address2のtokenの半分をaddress3に移動 + // address3が自分自身にtokenを追加でmint + await FractionToken.write.mint( + [hatId, address3.account?.address!, 5000n], + { account: address3.account! } + ); + + const tokenId = await FractionToken.read.getTokenId([ + hatId, + address2.account?.address!, + ]); + + // address2のtokenの半分をaddress4に移動 await FractionToken.write.safeTransferFrom( [ address2.account?.address!, - address3.account?.address!, + address4.account?.address!, tokenId, 5000n, "0x", ], - { - account: address2.account!, - } - ); - - // address1のtokenを自ら半分burnする - await FractionToken.write.burn( - [address1.account?.address!, address1.account?.address!, hatId, 5000n], - { - account: address1.account!, - } + { account: address2.account! } ); - // address3のtokenをaddress2によって半分burnする + // address2のtokenをaddress1が半分burnする await FractionToken.write.burn( - [address3.account?.address!, address2.account?.address!, hatId, 2500n], - { - account: address2.account!, - } + [ + address2.account?.address!, + address2.account?.address!, + hatId, + 2500n + ], + { account: address1.account! } ); - // 処理後のaddress1のbalance - balance = await FractionToken.read.balanceOf([ - address1.account?.address!, - address1.account?.address!, - hatId, - ]); - expect(balance).to.equal(5000n); - // 処理後のaddress2のbalance balance = await FractionToken.read.balanceOf([ address2.account?.address!, address2.account?.address!, hatId, ]); - expect(balance).to.equal(5000n); + expect(balance).to.equal(2500n); // 処理後のaddress3のbalance balance = await FractionToken.read.balanceOf([ address3.account?.address!, + address3.account?.address!, + hatId, + ]); + expect(balance).to.equal(15000n); + + // 処理後のaddress4のbalance + balance = await FractionToken.read.balanceOf([ + address4.account?.address!, address2.account?.address!, hatId, ]); - expect(balance).to.equal(2500n); + expect(balance).to.equal(5000n); }); it("should fail to mint a token", async () => { // roleのない人にtokenはmintできない await FractionToken.write - .mint([hatId, address3.account?.address!]) + .mintInitialSupply( + [hatId, address4.account?.address!], + { account: address1.account! } + ) + .catch((error: any) => { + expect(error.message).to.include("This account does not have the role"); + }); + + // 権限のない人はtokenをmintできない + await FractionToken.write + .mintInitialSupply( + [hatId, address2.account?.address!], + { account: address2.account! } + ) .catch((error: any) => { - expect(error.message).to.include("not authorized"); + expect(error.message).to.include("This msg.sender does not have the authority"); }); // tokenは二度mintできない await FractionToken.write - .mint([hatId, address1.account?.address!]) + .mintInitialSupply([hatId, address2.account?.address!]) + .catch((error: any) => { + expect(error.message).to.include("This account has already received"); + }); + + // initial supplyを受けていない場合は追加のmintはできない + await FractionToken.write + .mint( + [hatId, address4.account?.address!, 5000n], + { account: address4.account! } + ) + .catch((error: any) => { + expect(error.message).to.include("This account has not received the initial supply"); + }); + + // tokenの最初の受け取り手以外は追加でmintできない + await FractionToken.write + .mint( + [hatId, address2.account?.address!, 5000n], + { account: address4.account! } + ) .catch((error: any) => { - expect(error.message).to.include("already received"); + expect(error.message).to.include("Only the first recipient can additionally mint"); }); }); it("should fail to burn a token", async () => { - // address1のtokenはaddress2によってburnできない + // address2のtokenはaddress3によってburnできない await FractionToken.write - .burn([ - address1.account?.address!, - address1.account?.address!, - hatId, - 5000n, - ]) + .burn( + [ + address2.account?.address!, + address2.account?.address!, + hatId, + 5000n, + ], + { account: address3.account! } + ) .catch((error: any) => { - expect(error.message).to.include("not authorized"); + expect(error.message).to.include("Not authorized"); }); }); @@ -204,7 +261,7 @@ describe("FractionToken", () => { * Upgrade後に再度機能をテストする。 */ describe("Upgrade Test", () => { - it("upgrde", async () => { + it("upgrade", async () => { // FractionTokenをアップグレード const newFractionToken = await upgradeFractionToken( FractionToken.address, @@ -231,53 +288,52 @@ describe("FractionToken", () => { address2.account?.address!, ]); - // address2のtokenの半分をaddress3に移動 + // 現時点 address2: 2500n, address3: 15000n, address4: 5000n + + // address2のtokenの半分をaddress4に移動 await FractionToken.write.safeTransferFrom( [ address2.account?.address!, - address3.account?.address!, + address4.account?.address!, tokenId as bigint, - 5000n, + 1250n, "0x", ], - { - account: address2.account!, - } + { account: address2.account! } ); - let balance; - - // address1のbalance - balance = await newFractionToken.read.balanceOf([ - address1.account?.address!, - address1.account?.address!, - hatId, - ]); - expect(balance as bigint).to.equal(0n); - - // address3のtokenをaddress2によって半分burnする + // address2のtokenをaddress1によって半分burnする await newFractionToken.write.burn( - [address3.account?.address!, address2.account?.address!, hatId, 2500n], - { - account: address2.account!, - } + [address2.account?.address!, address2.account?.address!, hatId, 625n], + { account: address1.account! } ); + let balance; + // address2のbalance balance = await newFractionToken.read.balanceOf([ address2.account?.address!, address2.account?.address!, hatId, ]); - expect(balance as bigint).to.equal(0n); + expect(balance as bigint).to.equal(625n); // address3のbalance balance = await newFractionToken.read.balanceOf([ address3.account?.address!, + address3.account?.address!, + hatId, + ]); + expect(balance as bigint).to.equal(15000n); + + // address4のbalance + balance = await newFractionToken.read.balanceOf([ + address4.account?.address!, address2.account?.address!, hatId, ]); - expect(balance as bigint).to.equal(5000n); + + expect(balance as bigint).to.equal(6250n); }); it("should fail to mint a token after upgrade", async () => { @@ -288,18 +344,24 @@ describe("FractionToken", () => { ["", 10000n, Hats.address, zeroAddress] ); - // roleのない人にtokenはmintできない + // 権限のない人にtokenはmintできない await newFractionToken.write - .mint([hatId, address3.account?.address!]) + .mintInitialSupply( + [hatId, address4.account?.address!], + { account: address1.account! } + ) .catch((error: any) => { - expect(error.message).to.include("not authorized"); + expect(error.message).to.include("This account does not have the role"); }); // tokenは二度mintできない await newFractionToken.write - .mint([hatId, address1.account?.address!]) + .mintInitialSupply( + [hatId, address2.account?.address!], + { account: address1.account! } + ) .catch((error: any) => { - expect(error.message).to.include("already received"); + 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 8ac61bf..a35d081 100644 --- a/pkgs/contract/test/IntegrationTest.ts +++ b/pkgs/contract/test/IntegrationTest.ts @@ -235,8 +235,8 @@ describe("IntegrationTest", () => { it("should mint FractionToken", async () => { // address1,address2にtokenをmint - await FractionToken.write.mint([hat1_id, address1.account?.address!]); - await FractionToken.write.mint([hat1_id, address2.account?.address!]); + await FractionToken.write.mintInitialSupply([hat1_id, address1.account?.address!]); + await FractionToken.write.mintInitialSupply([hat1_id, address2.account?.address!]); // Check balance for address1 let balance1 = await FractionToken.read.balanceOf([ diff --git a/pkgs/contract/test/SplitsCreator.ts b/pkgs/contract/test/SplitsCreator.ts index a57b014..48dfc97 100644 --- a/pkgs/contract/test/SplitsCreator.ts +++ b/pkgs/contract/test/SplitsCreator.ts @@ -413,9 +413,9 @@ describe("CreateSplit", () => { }) .then((block) => block.timestamp); - await FractionToken.write.mint([hat1_id, address1.account?.address!]); - await FractionToken.write.mint([hat1_id, address2.account?.address!]); - await FractionToken.write.mint([hat2_id, address3.account?.address!]); + await FractionToken.write.mintInitialSupply([hat1_id, address1.account?.address!]); + await FractionToken.write.mintInitialSupply([hat1_id, address2.account?.address!]); + await FractionToken.write.mintInitialSupply([hat2_id, address3.account?.address!]); // let balance: bigint;