From 153802a2abe9fb793fe509b0b98a9ab5de4b881a Mon Sep 17 00:00:00 2001 From: aowheel Date: Tue, 29 Oct 2024 01:04:49 +0900 Subject: [PATCH] =?UTF-8?q?FractionToken=E3=81=AE=E6=A8=A9=E9=99=90?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E3=81=AE=E4=BF=AE=E6=AD=A3=E3=80=81=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=8A=E3=82=88=E3=81=B3=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contracts/fractiontoken/FractionToken.sol | 16 +- .../mock/FractionToken_Mock_v2.sol | 78 ++++++++-- pkgs/contract/test/FractionToken.ts | 146 ++++++++++-------- 3 files changed, 161 insertions(+), 79 deletions(-) diff --git a/pkgs/contract/contracts/fractiontoken/FractionToken.sol b/pkgs/contract/contracts/fractiontoken/FractionToken.sol index bd7eed8..b60ea98 100644 --- a/pkgs/contract/contracts/fractiontoken/FractionToken.sol +++ b/pkgs/contract/contracts/fractiontoken/FractionToken.sol @@ -28,9 +28,14 @@ contract FractionToken is ERC1155Upgradeable, ERC2771ContextUpgradeable { uint256 hatId, address account ) public { + require( + _hasHatRole(account, hatId), + "This account does not have the role" + ); + require( _hasHatAuthority(hatId), - "Not authorized" + "This msg.sender does not have the authority" ); uint256 tokenId = getTokenId(hatId, account); @@ -53,8 +58,15 @@ contract FractionToken is ERC1155Upgradeable, ERC2771ContextUpgradeable { 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"); + require( + _msgSender() == tokenRecipients[tokenId][0], + "Only the first recipient can additionally mint"); _mint(account, tokenId, amount, ""); } diff --git a/pkgs/contract/contracts/fractiontoken/mock/FractionToken_Mock_v2.sol b/pkgs/contract/contracts/fractiontoken/mock/FractionToken_Mock_v2.sol index 5cfa7e0..b1e1fd6 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,25 @@ 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 +81,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 +153,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 +174,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 +233,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/test/FractionToken.ts b/pkgs/contract/test/FractionToken.ts index 80539c5..7ae5197 100644 --- a/pkgs/contract/test/FractionToken.ts +++ b/pkgs/contract/test/FractionToken.ts @@ -104,28 +104,9 @@ describe("FractionToken", () => { { account: address1.account! } ); - // 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!, - ]); - // 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!, @@ -137,11 +118,30 @@ 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); + // 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( [ @@ -165,14 +165,6 @@ describe("FractionToken", () => { { 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!, @@ -189,7 +181,7 @@ describe("FractionToken", () => { ]); expect(balance).to.equal(15000n); - // address4のbalance + // 処理後のaddress4のbalance balance = await FractionToken.read.balanceOf([ address4.account?.address!, address2.account?.address!, @@ -199,11 +191,24 @@ describe("FractionToken", () => { }); it("should fail to mint a token", async () => { - // 権限のない人にtokenはmintできない + // roleのない人にtokenはmintできない await FractionToken.write - .mintInitialSupply([hatId, address4.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 FractionToken.write + .mintInitialSupply( + [hatId, address2.account?.address!], + { account: address2.account! } + ) + .catch((error: any) => { + expect(error.message).to.include("This msg.sender does not have the authority"); }); // tokenは二度mintできない @@ -213,6 +218,16 @@ describe("FractionToken", () => { 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( @@ -234,12 +249,10 @@ describe("FractionToken", () => { hatId, 5000n, ], - { - account: address3.account!, - } + { account: address3.account! } ) .catch((error: any) => { - expect(error.message).to.include("not authorized"); + expect(error.message).to.include("Not authorized"); }); }); @@ -248,7 +261,7 @@ describe("FractionToken", () => { * Upgrade後に再度機能をテストする。 */ describe("Upgrade Test", () => { - it("upgrde", async () => { + it("upgrade", async () => { // FractionTokenをアップグレード const newFractionToken = await upgradeFractionToken( FractionToken.address, @@ -275,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 () => { @@ -332,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"); }); }); });