From 4ee424059a1cd27b37559c682786bc660da57347 Mon Sep 17 00:00:00 2001 From: Maxime Date: Wed, 23 Oct 2024 19:28:31 +0300 Subject: [PATCH 1/8] feat: HypLSP8 implementation --- lukso-lsp8-contracts-0.16.0-rc-0.tgz | Bin 0 -> 36902 bytes package.json | 4 +- src/HypLSP8.sol | 79 +++++++++++++++++++++++++++ src/HypLSP8Collateral.sol | 75 +++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 lukso-lsp8-contracts-0.16.0-rc-0.tgz create mode 100644 src/HypLSP8.sol create mode 100644 src/HypLSP8Collateral.sol diff --git a/lukso-lsp8-contracts-0.16.0-rc-0.tgz b/lukso-lsp8-contracts-0.16.0-rc-0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..8c83074d2eaff7da1dbcd92b0f4172c41c05dc11 GIT binary patch literal 36902 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.8.19; + +import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; + +import { ILSP8IdentifiableDigitalAsset } from "@lukso/lsp8-contracts/contracts/ILSP8IdentifiableDigitalAsset.sol"; + +import {LSP8IdentifiableDigitalAssetInitAbstract} from "@lukso/lsp8-contracts/contracts/LSP8IdentifiableDigitalAssetInitAbstract.sol"; + +import { _LSP4_TOKEN_TYPE_TOKEN } from "@lukso/lsp4-contracts/contracts/LSP4Constants.sol"; + +import { _LSP8_TOKENID_FORMAT_NUMBER } from "@lukso/lsp8-contracts/contracts/LSP8Constants.sol"; + + + +/** + * @title Hyperlane LSP8 Token Router that extends LSP8 with remote transfer functionality. + * @author Abacus Works + */ +contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { + constructor(address _mailbox) TokenRouter(_mailbox) {} + + /** + * @notice Initializes the Hyperlane router, LSP8 metadata, and mints initial supply to deployer. + * @param _mintAmount The amount of NFTs to mint to `msg.sender`. + * @param _name The name of the token. + * @param _symbol The symbol of the token. + */ + function initialize( + uint256 _mintAmount, + string memory _name, + string memory _symbol + ) external initializer { + address owner = msg.sender; + _transferOwnership(owner); + + LSP8IdentifiableDigitalAssetInitAbstract._initialize(_name, _symbol, owner, _LSP4_TOKEN_TYPE_TOKEN, _LSP8_TOKENID_FORMAT_NUMBER); + + for (uint256 i = 0; i < _mintAmount; i++) { + _mint(owner, bytes32(i), true, ""); + } + } + + function balanceOf( + address _account + ) + public + view + virtual + override(TokenRouter, LSP8IdentifiableDigitalAssetInitAbstract, ILSP8IdentifiableDigitalAsset) + returns (uint256) + { + return LSP8IdentifiableDigitalAssetInitAbstract.balanceOf(_account); + } + + /** + * @dev Asserts `msg.sender` is owner and burns `_tokenId`. + * @inheritdoc TokenRouter + */ + function _transferFromSender( + uint256 _tokenId + ) internal virtual override returns (bytes memory) { + require(ownerOf(_tokenId) == msg.sender, "!owner"); + _burn(_tokenId, ""); + return bytes(""); // no metadata + } + + /** + * @dev Mints `_tokenId` to `_recipient`. + * @inheritdoc TokenRouter + */ + function _transferTo( + address _recipient, + uint256 _tokenId, + bytes calldata // no metadata + ) internal virtual override { + _mint(_recipient, _tokenId, true, ""); + } +} diff --git a/src/HypLSP8Collateral.sol b/src/HypLSP8Collateral.sol new file mode 100644 index 0000000..c8b489e --- /dev/null +++ b/src/HypLSP8Collateral.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.19; + +import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; + +import {TokenMessage} from "@hyperlane-xyz/core/contracts/token/libs/TokenMessage.sol"; + +import {ILSP8IdentifiableDigitalAsset} from "@lukso/lsp8-contracts/contracts/ILSP8IdentifiableDigitalAsset.sol"; + +/** + * @title Hyperlane LSP8 Token Collateral that wraps an existing LSP8 with remote transfer functionality. + * @author Abacus Works + */ +contract HypLSP8Collateral is TokenRouter { + ILSP8IdentifiableDigitalAsset public immutable wrappedToken; + + /** + * @notice Constructor + * @param lsp8 Address of the token to keep as collateral + */ + constructor(address lsp8, address _mailbox) TokenRouter(_mailbox) { + wrappedToken = ILSP8IdentifiableDigitalAsset(lsp8); + } + + /** + * @notice Initializes the Hyperlane router + * @param _hook The post-dispatch hook contract. + @param _interchainSecurityModule The interchain security module contract. + @param _owner The this contract. + */ + function initialize( + address _hook, + address _interchainSecurityModule, + address _owner + ) public virtual initializer { + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); + } + + function ownerOf(uint256 _tokenId) external view returns (address) { + return ILSP8IdentifiableDigitalAsset(wrappedToken).tokenOwnerOf(bytes32(_tokenId)); + } + + /** + * @dev Returns the balance of `_account` for `wrappedToken`. + * @inheritdoc TokenRouter + */ + function balanceOf( + address _account + ) external view override returns (uint256) { + return ILSP8IdentifiableDigitalAsset(wrappedToken).balanceOf(_account); + } + + /** + * @dev Transfers `_tokenId` of `wrappedToken` from `msg.sender` to this contract. + * @inheritdoc TokenRouter + */ + function _transferFromSender( + uint256 _tokenId + ) internal virtual override returns (bytes memory) { + wrappedToken.transfer(msg.sender, address(this), bytes32(_tokenId), true, ""); + return bytes(""); // no metadata + } + + /** + * @dev Transfers `_tokenId` of `wrappedToken` from this contract to `_recipient`. + * @inheritdoc TokenRouter + */ + function _transferTo( + address _recipient, + uint256 _tokenId, + bytes calldata // no metadata + ) internal override { + wrappedToken.transfer(address(this), _recipient, bytes32(_tokenId), true, ""); + } +} From 4de4028be2571327ee108ecf68c1d8521471dc6b Mon Sep 17 00:00:00 2001 From: Maxime Date: Thu, 24 Oct 2024 13:38:18 +0300 Subject: [PATCH 2/8] fix: lsp17 build error --- ...17contractextension-contracts-0.16.0-rc.0.tgz | Bin 0 -> 3686 bytes package.json | 3 ++- src/HypLSP8.sol | 10 ++++------ 3 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 lukso-lsp17contractextension-contracts-0.16.0-rc.0.tgz diff --git a/lukso-lsp17contractextension-contracts-0.16.0-rc.0.tgz b/lukso-lsp17contractextension-contracts-0.16.0-rc.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..b4e9a85e7d109b7c58e3ef65d00143552c55f97e GIT binary patch literal 3686 zcmV-s4w>;EiwFP!00002|Lq-XQyaH7pZzONxigJT?cMbY5}4eE64EjykAWn&cRFb- z@5;8q?rJM(F_iM(@94EJ+n6wfCO4}uw)HqVIy!F&ks$vRO|csgE?g1^_}uY7@&|WX zJkQ%23}9KkEL+1KJm?R%yl!`E%NxK0uiNwbTkybpa1SNr01d+5cow{sYGs{2t|h(A{2;|K4`DmjC-G0ro@0Fx*1$Z!~nN0w|9x z@%fI@1FK|ODyn$8TM%EkycX&9b$GQaI_TGcp9dsoVp=Ty4yaD1w+2TR$4l?hHtdxpfk*5wH)U;-J#=k znD2PHEx_DoBoU_lwPr4uPzGX#wb;SFfd{#w-~z<~Vu6Md5+BE0VYZhbe}+3fI{`|? zj567xe@BR@nVgCvYg%cLhIpYpvhCs`A@P(ibO;=~SYx9%ta{pTvabLe4F8!DhF2n> zI*~|%Sc!z{BQ6rW0u9yWs%>U>5ffx0H^bf3{fiY@ucB1zZl#)rfw(atp_=!1Juus8 zcXQL-bT|zy$nWx|7t}vp>`*{M&tdB2a&o+m5B8qDIB=psqVQg!aEdSHlm!JWj^6%y z^2&}mI(cos{uEOG)St;6H#H}QZFp7@Ato{g%c>8=3^SZY+3$BRvJo^S0TCBDLYjjM zTRu)n7)UD45DR8VfKQ`@gcv#5{M;A91S z|2?n>I>!@G9AJA-D2tHdo`hi`2xidf%o!0F9NQ-(vFd@j{00tmI`~|GBY4}%xJ>oh z;dZ$@u)%3Kll>nqZu>)tPGLfShW*o}9dlj^aDw9i$3DRY;ytAaj{m|*f0Ddq zFu<`O6N1?eq4i~frI1y?$!`UEHIlO9|H-vYE))> zppCn)toueqL^oQkhVG^H0g45D$fVW^?T3Tk-WMP(^JVUm-> zS1;8ZR%4Zxx!oK77o^GnsWVhduIPS2aS&n#T3_X;t#NM}rc(j83O*lRA0IqF{H=sm z(aE{B=2{{D2aHj6C&Is0{=42_A^sm|QL5$t%a{Mn%?513(*U0V!)KTY4)Ym~L8V%O zaEK{n0eTjUPtw>ID&vns>wDjlsz{9 zPR#hGk?u*QzO@ENu#7KJEzAyR363!paE3w>Kr_UggP5Th23o{{P9Q)6wF-D2#;W&n zTy^ht=;^Ku3r6r64q(D)R7ht?7;uU&G@%6`gC7b4CNXTv%ATjeEI}=!>z*E9P~(C! z96Zwq)=AQD&;QHK^BvfLYxRG(+ZzrS^#7JOto8p_;{WN>Ro>7CoN%dq!7Ht*RvrAf zsURVDNh}!U30C^i)g#q$;%){`g@d|fUmibU*0?=v1Hnuov9~T)j{mjJr=8ka5+@mm zO2OvfZeYABKy&A7qhKBc5g~>bir65iRPhi|mfKPk5|KD=4`Hav2vEl>{*yi+nZGE=su4QcF|{R$9n2)PQ0hKNNt^2^T=) zr9jRIl8(q6ndL|?0EMB1D`^)O{FMkLiNv4#$4l) zAyv(`5N?AiEdh!RWHDQJf-zhk>Mk9gChC5|p^d+;9MjoVpE=^yj!kp*089*(hj531 zYHC4}jzi+Z8NqWnBTS?y1bT)UBLSA4Ny=gl%`v56E4vZ_rXl+&O93=Id9o(R`}^hf3I5cpDAW! z#ac%TYZ}=(ax#!fg)R~j7H!!e{Z1k>_L!iM;}tylVPUSrST8qIDmUO#RTHDI%%>~a zvuQnK%>3d^ZD_$0_%OvHca4?KmfGSS3e$qbH94_{kFs0^3u?iZY`_>vai#IHkZ=V@ z--vxyM65UCXS3)wD8}Ahz6WMro|(97?AwyXRh8F13PTQ;*RAeKf|_Dc+!$-iKhdh% zSxr!GY`OG8(pWf>HJp zZkp^UdJ9!(#r2f7=w>OCw$!%1+MqK2Dw$ERR-^sBqoWbf#HRsnLxMQBPS7agQ%7bL zMj$sR4s0$&HrgbD-OWqcnafV>3Q!zCzgO;{pedq`s%w4fr(DozM_oHpE@HMg>A)%D zVH`BeB>94JaV<{*4EvazY1>!I>?qIBxSbqE6;CU}PHw>kO<_*cFo2MpDmOdBqcYn{ zyxIzgbapc*Nn;aWO&Y>;;m+2rG+P+b`A&saqtVDlUA9UYGvKN3YYm{N9^r^GY4mv- z3TeYK%pA!UmkRA2$X0OGwBTexW8&ag;wZ1p;GikT$ox+m@_laSqcc@&t(rn~g*LR| zM1}AbO{ln6<+JSEi<+=%)C(&w8h6(%i_8IMJ-bH!RC-Ny-W zJQcH%WVMj`NZ?=rCJHRXHuQTpfjZK;U74K{yb8};+3f^`tLP!79TJBm#vQ~tj>a}3 z$VRyEG|S!S**Y9eR&=+O723!WVqpj=vHQTuUl?|tK#61vgETWXD>P_V0hI?{HOk-W zyTSiV{`W8@V$WnNZkY>SlmG2+E!_Xv?)K{Q|F1RwTS(ZxO{NS5Sn|=jq-`h7`(Bg4 zEkLaE#C4wdo6i#$)pMRRE=Wn8KCaWpb^7?rP9K+yTAfL*Gs$%(xy~flndCZ?TxXK& zOmdw`t~1GXCb`Ze-+%sBN#tk$`>%d~tH0#$zqafA?^l}teW%}t$di?(qLQz@qie#S zmYbD-OSLvNyCRY5GmHc-=1i44DE%|}1C_n)8{|)O`@1T0YSX3%MW}0&r3s^F(pi*v z$_IeVbP=Dz8Dd1~d4@wIaM>v?r;^s@A8k;hx1$Yao4>h)qxyTn&y{bKn`0Ao#jr|7 zTWhvYLz7__O-&eWW3f^028~q6wIsx<`ri2*FZfhCQ_bAWTE{x8ki;+iP@7O8b>?+j zZ(W0{?g-d5|Gs0t88--@jL*24SW+g`W!|NhVYzk9wWQ2UAhq@TFh zJ<4scJ#wB~Vg8q0zf#u_)S+A~AIA=L1!d(!Z&f+I&=^QnO(qtyI*3gWLlG93@gpVi z{~==bN}Z>S%tJ7-)Tu;YIg~!q&>l}K`z+09ZV@bKX%zF*smyQ^4){;0qKy8Fv?EH(#_FB@-DNR!c-Cv*nFGOp1G{?I4 z{{{>3e}B-cOCt8#m+QiLrwpm1&gMI2jOwLdI<>)Q_AcPNgJbhNq5P*S z14q85x)LgmgSDD>>HEKT`~2^1-v90P>i_?DFQtC}_uGE|w=Q+5%ePzp2ecMp_yBkS E0CVvm>Hq)$ literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 1731409..eab3da7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "@hyperlane-xyz/core": "^5.3.0", "@lukso/lsp4-contracts": "lukso-lsp4-contracts-0.16.0-rc.0.tgz", "@lukso/lsp7-contracts": "lukso-lsp7-contracts-0.16.0-rc.0.tgz", - "@lukso/lsp8-contracts": "lukso-lsp8-contracts-0.16.0-rc-0.tgz" + "@lukso/lsp8-contracts": "lukso-lsp8-contracts-0.16.0-rc-0.tgz", + "@lukso/lsp17contractextension-contracts": "lukso-lsp17contractextension-contracts-0.16.0-rc.0.tgz" }, "devDependencies": { diff --git a/src/HypLSP8.sol b/src/HypLSP8.sol index 84ef179..de65dc9 100644 --- a/src/HypLSP8.sol +++ b/src/HypLSP8.sol @@ -3,8 +3,6 @@ pragma solidity >=0.8.19; import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; -import { ILSP8IdentifiableDigitalAsset } from "@lukso/lsp8-contracts/contracts/ILSP8IdentifiableDigitalAsset.sol"; - import {LSP8IdentifiableDigitalAssetInitAbstract} from "@lukso/lsp8-contracts/contracts/LSP8IdentifiableDigitalAssetInitAbstract.sol"; import { _LSP4_TOKEN_TYPE_TOKEN } from "@lukso/lsp4-contracts/contracts/LSP4Constants.sol"; @@ -47,7 +45,7 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { public view virtual - override(TokenRouter, LSP8IdentifiableDigitalAssetInitAbstract, ILSP8IdentifiableDigitalAsset) + override(TokenRouter, LSP8IdentifiableDigitalAssetInitAbstract) returns (uint256) { return LSP8IdentifiableDigitalAssetInitAbstract.balanceOf(_account); @@ -60,8 +58,8 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { function _transferFromSender( uint256 _tokenId ) internal virtual override returns (bytes memory) { - require(ownerOf(_tokenId) == msg.sender, "!owner"); - _burn(_tokenId, ""); + require(tokenOwnerOf(bytes32(_tokenId)) == msg.sender, "!owner"); + _burn(bytes32(_tokenId), ""); return bytes(""); // no metadata } @@ -74,6 +72,6 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { uint256 _tokenId, bytes calldata // no metadata ) internal virtual override { - _mint(_recipient, _tokenId, true, ""); + _mint(_recipient, bytes32(_tokenId), true, ""); } } From 34402b2e6f8d98f9afba9d2efc0f8d06c8a1b0c8 Mon Sep 17 00:00:00 2001 From: Maxime Date: Tue, 29 Oct 2024 21:42:41 +0000 Subject: [PATCH 3/8] feat: create lsp8Mock --- test/LSP8Mock.sol | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 test/LSP8Mock.sol diff --git a/test/LSP8Mock.sol b/test/LSP8Mock.sol new file mode 100644 index 0000000..90be273 --- /dev/null +++ b/test/LSP8Mock.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import { LSP8IdentifiableDigitalAsset } from "@lukso/lsp8-contracts/contracts/LSP8IdentifiableDigitalAsset.sol"; + +contract LSP8Mock is LSP8IdentifiableDigitalAsset { + constructor( + string memory name_, + string memory symbol_, + address owner_ + ) LSP8IdentifiableDigitalAsset(name_, symbol_, owner_, 0, 0) {} + + function mint( + address to, + bytes32 tokenId, + bool force, + bytes memory data + ) public { + _mint(to, tokenId, force, data); + } + + function mintBatch( + address[] memory to, + bytes32[] memory tokenIds, + bool[] memory force, + bytes[] memory data + ) public { + require( + to.length == tokenIds.length && + tokenIds.length == force.length && + force.length == data.length, + "LSP8Mock: array length mismatch" + ); + + for(uint256 i = 0; i < to.length; i++) { + _mint(to[i], tokenIds[i], force[i], data[i]); + } + } + + function mintIdRange( + address to, + uint256 startId, + uint256 amount, + bool force, + bytes memory data + ) public { + for(uint256 i = 0; i < amount; i++) { + bytes32 tokenId = bytes32(startId + i); + _mint(to, tokenId, force, data); + } + } + + function burn( + bytes32 tokenId, + bytes memory data + ) public { + _burn(tokenId, data); + } + + function burnBatch( + bytes32[] memory tokenIds, + bytes[] memory data + ) public { + require( + tokenIds.length == data.length, + "LSP8Mock: array length mismatch" + ); + + for(uint256 i = 0; i < tokenIds.length; i++) { + _burn(tokenIds[i], data[i]); + } + } + + function setData( + bytes32 tokenId, + bytes32 dataKey, + bytes memory dataValue + ) public { + _setData( dataKey, dataValue); + } + + function transferBatch( + address[] memory from, + address[] memory to, + bytes32[] memory tokenIds, + bool[] memory force, + bytes[] memory data + ) public override { + require( + from.length == to.length && + to.length == tokenIds.length && + tokenIds.length == force.length && + force.length == data.length, + "LSP8Mock: array length mismatch" + ); + + for(uint256 i = 0; i < from.length; i++) { + transfer(from[i], to[i], tokenIds[i], force[i], data[i]); + } + } + + // Override for testing purposes - allows easy token ID verification + function tokenURI(bytes32 tokenId) public pure returns (string memory) { + return "TEST-BASE-URI"; + } + + // Helper function for testing + function exists(bytes32 tokenId) public view returns (bool) { + return _exists(tokenId); + } +} From 752d76a5ee48919b0ca0eb7ed9264f2c0f9a6167 Mon Sep 17 00:00:00 2001 From: Maxime Date: Tue, 29 Oct 2024 21:43:08 +0000 Subject: [PATCH 4/8] feat: initial test for hypLSP8 --- test/HypLSP8.t.sol | 224 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 test/HypLSP8.t.sol diff --git a/test/HypLSP8.t.sol b/test/HypLSP8.t.sol new file mode 100644 index 0000000..094ee16 --- /dev/null +++ b/test/HypLSP8.t.sol @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import { Test } from "forge-std/src/Test.sol"; + +import { TypeCasts } from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; +import { TestMailbox } from "@hyperlane-xyz/core/contracts/test/TestMailbox.sol"; +import { TestPostDispatchHook } from "@hyperlane-xyz/core/contracts/test/TestPostDispatchHook.sol"; +import { TestInterchainGasPaymaster } from "@hyperlane-xyz/core/contracts/test/TestInterchainGasPaymaster.sol"; +import { GasRouter } from "@hyperlane-xyz/core/contracts/client/GasRouter.sol"; +import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; + +// Mock + contracts to test +import { HypLSP8 } from "../src/HypLSP8.sol"; +import { HypLSP8Collateral } from "../src/HypLSP8Collateral.sol"; +import { LSP8Mock } from "./LSP8Mock.sol"; + +import "forge-std/src/console.sol"; + +abstract contract HypTokenTest is Test { + using TypeCasts for address; + + uint256 internal constant INITIAL_SUPPLY = 10; + string internal constant NAME = "Hyperlane NFTs"; + string internal constant SYMBOL = "HNFT"; + + address internal ALICE = makeAddr("alice"); + address internal BOB = makeAddr("bob"); + address internal OWNER = makeAddr("owner"); + uint32 internal constant ORIGIN = 11; + uint32 internal constant DESTINATION = 22; + bytes32 internal constant TOKEN_ID = bytes32(uint256(1)); + string internal constant URI = "http://example.com/token/"; + + LSP8Mock internal localPrimaryToken; + LSP8Mock internal remotePrimaryToken; + TestMailbox internal localMailbox; + TestMailbox internal remoteMailbox; + TokenRouter internal localToken; + HypLSP8 internal remoteToken; + TestPostDispatchHook internal noopHook; + TestInterchainGasPaymaster internal igp; + + event SentTransferRemote( + uint32 indexed destination, + bytes32 indexed recipient, + bytes32 tokenId + ); + + event ReceivedTransferRemote( + uint32 indexed origin, + bytes32 indexed recipient, + bytes32 tokenId + ); + + function setUp() public virtual { + localMailbox = new TestMailbox(ORIGIN); + remoteMailbox = new TestMailbox(DESTINATION); + + localPrimaryToken = new LSP8Mock(NAME, SYMBOL, OWNER); + + noopHook = new TestPostDispatchHook(); + localMailbox.setDefaultHook(address(noopHook)); + localMailbox.setRequiredHook(address(noopHook)); + + remoteToken = new HypLSP8(address(remoteMailbox)); + + vm.prank(OWNER); + // remoteToken.initialize(100, NAME, SYMBOL); + remoteToken.initialize(100, NAME, SYMBOL); + + igp = new TestInterchainGasPaymaster(); + + vm.deal(ALICE, 125_000); + } + + function _enrollRemoteTokenRouter() internal { + vm.prank(OWNER); + remoteToken.enrollRemoteRouter(ORIGIN, address(localToken).addressToBytes32()); + } + + function _processTransfers(address _recipient, bytes32 _tokenId) internal { + vm.prank(address(remoteMailbox)); + remoteToken.handle( + ORIGIN, + address(localToken).addressToBytes32(), + abi.encodePacked(_recipient.addressToBytes32(), _tokenId) + ); + } + + function _performRemoteTransfer(uint256 _msgValue, bytes32 _tokenId) internal { + vm.prank(ALICE); + localToken.transferRemote{ value: _msgValue }( + DESTINATION, + BOB.addressToBytes32(), + uint256(_tokenId) + ); + + vm.expectEmit(true, true, false, true); + emit ReceivedTransferRemote(ORIGIN, BOB.addressToBytes32(), _tokenId); + _processTransfers(BOB, _tokenId); + + assertEq(remoteToken.balanceOf(BOB), 1); + } +} + +contract HypLSP8Test is HypTokenTest { + using TypeCasts for address; + + HypLSP8 internal lsp8Token; + + function setUp() public override { + super.setUp(); + + localToken = new HypLSP8(address(localMailbox)); + lsp8Token = HypLSP8(payable(address(localToken))); + + vm.prank(OWNER); + lsp8Token.initialize(INITIAL_SUPPLY, NAME, SYMBOL); + + vm.prank(OWNER); + lsp8Token.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); + + // // Transfer some tokens to ALICE for testing + vm.prank(OWNER); + lsp8Token.transfer(OWNER, ALICE, TOKEN_ID, true, ""); + + // _enrollRemoteTokenRouter(); + } + + function testInitialize_revert_ifAlreadyInitialized() public { + vm.expectRevert("Initializable: contract is already initialized"); + lsp8Token.initialize(INITIAL_SUPPLY, NAME, SYMBOL); + } + + function testTotalSupply() public { + assertEq(lsp8Token.totalSupply(), INITIAL_SUPPLY); + } + + function testTokenOwnerOf() public { + assertEq(lsp8Token.tokenOwnerOf(TOKEN_ID), ALICE); + } + + function testLocalTransfer() public { + vm.prank(ALICE); + lsp8Token.transfer(ALICE, BOB, TOKEN_ID, true, ""); + assertEq(lsp8Token.tokenOwnerOf(TOKEN_ID), BOB); + assertEq(lsp8Token.balanceOf(ALICE), 0); + assertEq(lsp8Token.balanceOf(BOB), 1); + } + + function testRemoteTransfer() public { + vm.prank(OWNER); + remoteToken.enrollRemoteRouter(ORIGIN, address(localToken).addressToBytes32()); + + // vm.expectEmit(true, true, false, true); + // emit SentTransferRemote(DESTINATION, BOB.addressToBytes32(), TOKEN_ID); + + _performRemoteTransfer(25000, TOKEN_ID); + assertEq(lsp8Token.balanceOf(ALICE), 0); + } + + function testRemoteTransfer_revert_unauthorizedOperator() public { + vm.prank(BOB); + vm.expectRevert("LSP8: caller is not owner nor operator"); + localToken.transferRemote{ value: 25000 }( + DESTINATION, + BOB.addressToBytes32(), + uint256(TOKEN_ID) + ); + } + + function testRemoteTransfer_revert_invalidTokenId() public { + bytes32 invalidTokenId = bytes32(uint256(999)); + vm.expectRevert("LSP8: token does not exist"); + _performRemoteTransfer(25000, invalidTokenId); + } +} + +contract HypLSP8CollateralTest is HypTokenTest { + using TypeCasts for address; + + HypLSP8Collateral internal lsp8Collateral; + + function setUp() public override { + super.setUp(); + + localToken = new HypLSP8Collateral(address(localPrimaryToken), address(localMailbox)); + lsp8Collateral = HypLSP8Collateral(address(localToken)); + + vm.prank(OWNER); + lsp8Collateral.initialize(address(noopHook), address(0), OWNER); + + vm.prank(OWNER); + lsp8Collateral.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); + + // Mint test tokens + vm.startPrank(OWNER); + localPrimaryToken.mint(OWNER, TOKEN_ID, true, ""); + localPrimaryToken.transfer(OWNER, ALICE, TOKEN_ID, true, ""); + vm.stopPrank(); + + _enrollRemoteTokenRouter(); + } + + function testRemoteTransfer() public { + vm.prank(ALICE); + localPrimaryToken.authorizeOperator(address(lsp8Collateral), TOKEN_ID, ""); + _performRemoteTransfer(25000, TOKEN_ID); + + assertEq(localPrimaryToken.tokenOwnerOf(TOKEN_ID), address(lsp8Collateral)); + } + + function testRemoteTransfer_revert_unauthorized() public { + vm.expectRevert("LSP8: caller is not owner nor operator"); + _performRemoteTransfer(25000, TOKEN_ID); + } + + function testRemoteTransfer_revert_invalidTokenId() public { + bytes32 invalidTokenId = bytes32(uint256(999)); + vm.expectRevert("LSP8: token does not exist"); + _performRemoteTransfer(25000, invalidTokenId); + } +} From 55640ed368310af8d74cc0c4f6b9444bba4b3d3c Mon Sep 17 00:00:00 2001 From: Maxime Date: Wed, 30 Oct 2024 16:52:52 +0000 Subject: [PATCH 5/8] test: fix remoteTransfer test --- test/HypLSP8.t.sol | 103 ++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/test/HypLSP8.t.sol b/test/HypLSP8.t.sol index 094ee16..2cde431 100644 --- a/test/HypLSP8.t.sol +++ b/test/HypLSP8.t.sol @@ -15,7 +15,6 @@ import { HypLSP8 } from "../src/HypLSP8.sol"; import { HypLSP8Collateral } from "../src/HypLSP8Collateral.sol"; import { LSP8Mock } from "./LSP8Mock.sol"; -import "forge-std/src/console.sol"; abstract contract HypTokenTest is Test { using TypeCasts for address; @@ -39,19 +38,6 @@ abstract contract HypTokenTest is Test { TokenRouter internal localToken; HypLSP8 internal remoteToken; TestPostDispatchHook internal noopHook; - TestInterchainGasPaymaster internal igp; - - event SentTransferRemote( - uint32 indexed destination, - bytes32 indexed recipient, - bytes32 tokenId - ); - - event ReceivedTransferRemote( - uint32 indexed origin, - bytes32 indexed recipient, - bytes32 tokenId - ); function setUp() public virtual { localMailbox = new TestMailbox(ORIGIN); @@ -66,15 +52,21 @@ abstract contract HypTokenTest is Test { remoteToken = new HypLSP8(address(remoteMailbox)); vm.prank(OWNER); - // remoteToken.initialize(100, NAME, SYMBOL); - remoteToken.initialize(100, NAME, SYMBOL); + remoteToken.initialize(0, NAME, SYMBOL); - igp = new TestInterchainGasPaymaster(); - - vm.deal(ALICE, 125_000); + vm.deal(ALICE, 1 ether); } - function _enrollRemoteTokenRouter() internal { + function _deployRemoteToken(bool isCollateral) internal { + if (isCollateral) { + remoteToken = new HypLSP8(address(remoteMailbox)); + vm.prank(OWNER); + remoteToken.initialize(0, NAME, SYMBOL); + } else { + remoteToken = new HypLSP8(address(remoteMailbox)); + vm.prank(OWNER); + remoteToken.initialize(0, NAME, SYMBOL); + } vm.prank(OWNER); remoteToken.enrollRemoteRouter(ORIGIN, address(localToken).addressToBytes32()); } @@ -96,10 +88,7 @@ abstract contract HypTokenTest is Test { uint256(_tokenId) ); - vm.expectEmit(true, true, false, true); - emit ReceivedTransferRemote(ORIGIN, BOB.addressToBytes32(), _tokenId); _processTransfers(BOB, _tokenId); - assertEq(remoteToken.balanceOf(BOB), 1); } } @@ -121,11 +110,15 @@ contract HypLSP8Test is HypTokenTest { vm.prank(OWNER); lsp8Token.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); - // // Transfer some tokens to ALICE for testing + vm.deal(OWNER, 1 ether); + vm.deal(ALICE, 1 ether); + vm.deal(BOB, 1 ether); + + // Transfer some tokens to ALICE for testing vm.prank(OWNER); lsp8Token.transfer(OWNER, ALICE, TOKEN_ID, true, ""); - // _enrollRemoteTokenRouter(); + _deployRemoteToken(false); } function testInitialize_revert_ifAlreadyInitialized() public { @@ -149,20 +142,17 @@ contract HypLSP8Test is HypTokenTest { assertEq(lsp8Token.balanceOf(BOB), 1); } - function testRemoteTransfer() public { + function testRemoteTransferHere() public { vm.prank(OWNER); - remoteToken.enrollRemoteRouter(ORIGIN, address(localToken).addressToBytes32()); - - // vm.expectEmit(true, true, false, true); - // emit SentTransferRemote(DESTINATION, BOB.addressToBytes32(), TOKEN_ID); + remoteToken.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); _performRemoteTransfer(25000, TOKEN_ID); assertEq(lsp8Token.balanceOf(ALICE), 0); } function testRemoteTransfer_revert_unauthorizedOperator() public { - vm.prank(BOB); - vm.expectRevert("LSP8: caller is not owner nor operator"); + vm.prank(OWNER); + vm.expectRevert("!owner"); localToken.transferRemote{ value: 25000 }( DESTINATION, BOB.addressToBytes32(), @@ -172,7 +162,12 @@ contract HypLSP8Test is HypTokenTest { function testRemoteTransfer_revert_invalidTokenId() public { bytes32 invalidTokenId = bytes32(uint256(999)); - vm.expectRevert("LSP8: token does not exist"); + vm.expectRevert( + abi.encodeWithSignature( + "LSP8NonExistentTokenId(bytes32)", + invalidTokenId + ) + ); _performRemoteTransfer(25000, invalidTokenId); } } @@ -191,8 +186,10 @@ contract HypLSP8CollateralTest is HypTokenTest { vm.prank(OWNER); lsp8Collateral.initialize(address(noopHook), address(0), OWNER); - vm.prank(OWNER); - lsp8Collateral.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); + // Give accounts some ETH for gas + vm.deal(OWNER, 1 ether); + vm.deal(ALICE, 1 ether); + vm.deal(BOB, 1 ether); // Mint test tokens vm.startPrank(OWNER); @@ -200,7 +197,17 @@ contract HypLSP8CollateralTest is HypTokenTest { localPrimaryToken.transfer(OWNER, ALICE, TOKEN_ID, true, ""); vm.stopPrank(); - _enrollRemoteTokenRouter(); + // Deploy remote token + remoteToken = new HypLSP8(address(remoteMailbox)); + vm.prank(OWNER); + remoteToken.initialize(0, NAME, SYMBOL); + + // Enroll routers for both chains + vm.prank(OWNER); + lsp8Collateral.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); + + vm.prank(OWNER); + remoteToken.enrollRemoteRouter(ORIGIN, address(localToken).addressToBytes32()); } function testRemoteTransfer() public { @@ -212,13 +219,31 @@ contract HypLSP8CollateralTest is HypTokenTest { } function testRemoteTransfer_revert_unauthorized() public { - vm.expectRevert("LSP8: caller is not owner nor operator"); - _performRemoteTransfer(25000, TOKEN_ID); + + + vm.expectRevert( + abi.encodeWithSignature( + "LSP8NotTokenOperator(bytes32,address)", + TOKEN_ID, + address(lsp8Collateral) + ) + ); + vm.prank(BOB); + localToken.transferRemote{ value: 25000 }( + DESTINATION, + BOB.addressToBytes32(), + uint256(TOKEN_ID) + ); } function testRemoteTransfer_revert_invalidTokenId() public { bytes32 invalidTokenId = bytes32(uint256(999)); - vm.expectRevert("LSP8: token does not exist"); + vm.expectRevert( + abi.encodeWithSignature( + "LSP8NonExistentTokenId(bytes32)", + invalidTokenId + ) + ); _performRemoteTransfer(25000, invalidTokenId); } } From 92f3d4ae824b792cf828aacc05a5bcfaa092d868 Mon Sep 17 00:00:00 2001 From: Maxime Date: Wed, 30 Oct 2024 16:53:33 +0000 Subject: [PATCH 6/8] refactor: prettiy package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index eab3da7..9178e9e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "@lukso/lsp7-contracts": "lukso-lsp7-contracts-0.16.0-rc.0.tgz", "@lukso/lsp8-contracts": "lukso-lsp8-contracts-0.16.0-rc-0.tgz", "@lukso/lsp17contractextension-contracts": "lukso-lsp17contractextension-contracts-0.16.0-rc.0.tgz" - }, "devDependencies": { "forge-std": "github:foundry-rs/forge-std#v1.8.1", From ffa22482526b8617de8d5d79cd8a1e7600a135fe Mon Sep 17 00:00:00 2001 From: Maxime Date: Wed, 30 Oct 2024 16:57:31 +0000 Subject: [PATCH 7/8] fix: solhint errors --- src/HypLSP8.sol | 31 ++++++++---------- src/HypLSP8Collateral.sol | 27 +++++++-------- test/HypLSP8.t.sol | 55 +++++++------------------------ test/LSP8Mock.sol | 69 ++++++++++++++------------------------- 4 files changed, 61 insertions(+), 121 deletions(-) diff --git a/src/HypLSP8.sol b/src/HypLSP8.sol index de65dc9..de265ea 100644 --- a/src/HypLSP8.sol +++ b/src/HypLSP8.sol @@ -3,20 +3,19 @@ pragma solidity >=0.8.19; import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; -import {LSP8IdentifiableDigitalAssetInitAbstract} from "@lukso/lsp8-contracts/contracts/LSP8IdentifiableDigitalAssetInitAbstract.sol"; +import { LSP8IdentifiableDigitalAssetInitAbstract } from + "@lukso/lsp8-contracts/contracts/LSP8IdentifiableDigitalAssetInitAbstract.sol"; import { _LSP4_TOKEN_TYPE_TOKEN } from "@lukso/lsp4-contracts/contracts/LSP4Constants.sol"; import { _LSP8_TOKENID_FORMAT_NUMBER } from "@lukso/lsp8-contracts/contracts/LSP8Constants.sol"; - - /** * @title Hyperlane LSP8 Token Router that extends LSP8 with remote transfer functionality. * @author Abacus Works */ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { - constructor(address _mailbox) TokenRouter(_mailbox) {} + constructor(address _mailbox) TokenRouter(_mailbox) { } /** * @notice Initializes the Hyperlane router, LSP8 metadata, and mints initial supply to deployer. @@ -24,24 +23,20 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { * @param _name The name of the token. * @param _symbol The symbol of the token. */ - function initialize( - uint256 _mintAmount, - string memory _name, - string memory _symbol - ) external initializer { + function initialize(uint256 _mintAmount, string memory _name, string memory _symbol) external initializer { address owner = msg.sender; _transferOwnership(owner); - LSP8IdentifiableDigitalAssetInitAbstract._initialize(_name, _symbol, owner, _LSP4_TOKEN_TYPE_TOKEN, _LSP8_TOKENID_FORMAT_NUMBER); + LSP8IdentifiableDigitalAssetInitAbstract._initialize( + _name, _symbol, owner, _LSP4_TOKEN_TYPE_TOKEN, _LSP8_TOKENID_FORMAT_NUMBER + ); for (uint256 i = 0; i < _mintAmount; i++) { _mint(owner, bytes32(i), true, ""); } } - function balanceOf( - address _account - ) + function balanceOf(address _account) public view virtual @@ -55,9 +50,7 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { * @dev Asserts `msg.sender` is owner and burns `_tokenId`. * @inheritdoc TokenRouter */ - function _transferFromSender( - uint256 _tokenId - ) internal virtual override returns (bytes memory) { + function _transferFromSender(uint256 _tokenId) internal virtual override returns (bytes memory) { require(tokenOwnerOf(bytes32(_tokenId)) == msg.sender, "!owner"); _burn(bytes32(_tokenId), ""); return bytes(""); // no metadata @@ -71,7 +64,11 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { address _recipient, uint256 _tokenId, bytes calldata // no metadata - ) internal virtual override { + ) + internal + virtual + override + { _mint(_recipient, bytes32(_tokenId), true, ""); } } diff --git a/src/HypLSP8Collateral.sol b/src/HypLSP8Collateral.sol index c8b489e..bd155ef 100644 --- a/src/HypLSP8Collateral.sol +++ b/src/HypLSP8Collateral.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.19; import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; -import {TokenMessage} from "@hyperlane-xyz/core/contracts/token/libs/TokenMessage.sol"; +import { TokenMessage } from "@hyperlane-xyz/core/contracts/token/libs/TokenMessage.sol"; -import {ILSP8IdentifiableDigitalAsset} from "@lukso/lsp8-contracts/contracts/ILSP8IdentifiableDigitalAsset.sol"; +import { ILSP8IdentifiableDigitalAsset } from "@lukso/lsp8-contracts/contracts/ILSP8IdentifiableDigitalAsset.sol"; /** * @title Hyperlane LSP8 Token Collateral that wraps an existing LSP8 with remote transfer functionality. @@ -25,14 +25,10 @@ contract HypLSP8Collateral is TokenRouter { /** * @notice Initializes the Hyperlane router * @param _hook The post-dispatch hook contract. - @param _interchainSecurityModule The interchain security module contract. - @param _owner The this contract. + * @param _interchainSecurityModule The interchain security module contract. + * @param _owner The this contract. */ - function initialize( - address _hook, - address _interchainSecurityModule, - address _owner - ) public virtual initializer { + function initialize(address _hook, address _interchainSecurityModule, address _owner) public virtual initializer { _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); } @@ -44,9 +40,7 @@ contract HypLSP8Collateral is TokenRouter { * @dev Returns the balance of `_account` for `wrappedToken`. * @inheritdoc TokenRouter */ - function balanceOf( - address _account - ) external view override returns (uint256) { + function balanceOf(address _account) external view override returns (uint256) { return ILSP8IdentifiableDigitalAsset(wrappedToken).balanceOf(_account); } @@ -54,9 +48,7 @@ contract HypLSP8Collateral is TokenRouter { * @dev Transfers `_tokenId` of `wrappedToken` from `msg.sender` to this contract. * @inheritdoc TokenRouter */ - function _transferFromSender( - uint256 _tokenId - ) internal virtual override returns (bytes memory) { + function _transferFromSender(uint256 _tokenId) internal virtual override returns (bytes memory) { wrappedToken.transfer(msg.sender, address(this), bytes32(_tokenId), true, ""); return bytes(""); // no metadata } @@ -69,7 +61,10 @@ contract HypLSP8Collateral is TokenRouter { address _recipient, uint256 _tokenId, bytes calldata // no metadata - ) internal override { + ) + internal + override + { wrappedToken.transfer(address(this), _recipient, bytes32(_tokenId), true, ""); } } diff --git a/test/HypLSP8.t.sol b/test/HypLSP8.t.sol index 2cde431..f66f4a0 100644 --- a/test/HypLSP8.t.sol +++ b/test/HypLSP8.t.sol @@ -15,7 +15,6 @@ import { HypLSP8 } from "../src/HypLSP8.sol"; import { HypLSP8Collateral } from "../src/HypLSP8Collateral.sol"; import { LSP8Mock } from "./LSP8Mock.sol"; - abstract contract HypTokenTest is Test { using TypeCasts for address; @@ -74,19 +73,13 @@ abstract contract HypTokenTest is Test { function _processTransfers(address _recipient, bytes32 _tokenId) internal { vm.prank(address(remoteMailbox)); remoteToken.handle( - ORIGIN, - address(localToken).addressToBytes32(), - abi.encodePacked(_recipient.addressToBytes32(), _tokenId) + ORIGIN, address(localToken).addressToBytes32(), abi.encodePacked(_recipient.addressToBytes32(), _tokenId) ); } function _performRemoteTransfer(uint256 _msgValue, bytes32 _tokenId) internal { vm.prank(ALICE); - localToken.transferRemote{ value: _msgValue }( - DESTINATION, - BOB.addressToBytes32(), - uint256(_tokenId) - ); + localToken.transferRemote{ value: _msgValue }(DESTINATION, BOB.addressToBytes32(), uint256(_tokenId)); _processTransfers(BOB, _tokenId); assertEq(remoteToken.balanceOf(BOB), 1); @@ -146,29 +139,20 @@ contract HypLSP8Test is HypTokenTest { vm.prank(OWNER); remoteToken.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); - _performRemoteTransfer(25000, TOKEN_ID); + _performRemoteTransfer(25_000, TOKEN_ID); assertEq(lsp8Token.balanceOf(ALICE), 0); } function testRemoteTransfer_revert_unauthorizedOperator() public { vm.prank(OWNER); vm.expectRevert("!owner"); - localToken.transferRemote{ value: 25000 }( - DESTINATION, - BOB.addressToBytes32(), - uint256(TOKEN_ID) - ); + localToken.transferRemote{ value: 25_000 }(DESTINATION, BOB.addressToBytes32(), uint256(TOKEN_ID)); } function testRemoteTransfer_revert_invalidTokenId() public { bytes32 invalidTokenId = bytes32(uint256(999)); - vm.expectRevert( - abi.encodeWithSignature( - "LSP8NonExistentTokenId(bytes32)", - invalidTokenId - ) - ); - _performRemoteTransfer(25000, invalidTokenId); + vm.expectRevert(abi.encodeWithSignature("LSP8NonExistentTokenId(bytes32)", invalidTokenId)); + _performRemoteTransfer(25_000, invalidTokenId); } } @@ -213,37 +197,22 @@ contract HypLSP8CollateralTest is HypTokenTest { function testRemoteTransfer() public { vm.prank(ALICE); localPrimaryToken.authorizeOperator(address(lsp8Collateral), TOKEN_ID, ""); - _performRemoteTransfer(25000, TOKEN_ID); + _performRemoteTransfer(25_000, TOKEN_ID); assertEq(localPrimaryToken.tokenOwnerOf(TOKEN_ID), address(lsp8Collateral)); } function testRemoteTransfer_revert_unauthorized() public { - - vm.expectRevert( - abi.encodeWithSignature( - "LSP8NotTokenOperator(bytes32,address)", - TOKEN_ID, - address(lsp8Collateral) - ) - ); - vm.prank(BOB); - localToken.transferRemote{ value: 25000 }( - DESTINATION, - BOB.addressToBytes32(), - uint256(TOKEN_ID) + abi.encodeWithSignature("LSP8NotTokenOperator(bytes32,address)", TOKEN_ID, address(lsp8Collateral)) ); + vm.prank(BOB); + localToken.transferRemote{ value: 25_000 }(DESTINATION, BOB.addressToBytes32(), uint256(TOKEN_ID)); } function testRemoteTransfer_revert_invalidTokenId() public { bytes32 invalidTokenId = bytes32(uint256(999)); - vm.expectRevert( - abi.encodeWithSignature( - "LSP8NonExistentTokenId(bytes32)", - invalidTokenId - ) - ); - _performRemoteTransfer(25000, invalidTokenId); + vm.expectRevert(abi.encodeWithSignature("LSP8NonExistentTokenId(bytes32)", invalidTokenId)); + _performRemoteTransfer(25_000, invalidTokenId); } } diff --git a/test/LSP8Mock.sol b/test/LSP8Mock.sol index 90be273..9a5815a 100644 --- a/test/LSP8Mock.sol +++ b/test/LSP8Mock.sol @@ -8,14 +8,11 @@ contract LSP8Mock is LSP8IdentifiableDigitalAsset { string memory name_, string memory symbol_, address owner_ - ) LSP8IdentifiableDigitalAsset(name_, symbol_, owner_, 0, 0) {} + ) + LSP8IdentifiableDigitalAsset(name_, symbol_, owner_, 0, 0) + { } - function mint( - address to, - bytes32 tokenId, - bool force, - bytes memory data - ) public { + function mint(address to, bytes32 tokenId, bool force, bytes memory data) public { _mint(to, tokenId, force, data); } @@ -24,59 +21,40 @@ contract LSP8Mock is LSP8IdentifiableDigitalAsset { bytes32[] memory tokenIds, bool[] memory force, bytes[] memory data - ) public { + ) + public + { require( - to.length == tokenIds.length && - tokenIds.length == force.length && - force.length == data.length, + to.length == tokenIds.length && tokenIds.length == force.length && force.length == data.length, "LSP8Mock: array length mismatch" ); - for(uint256 i = 0; i < to.length; i++) { + for (uint256 i = 0; i < to.length; i++) { _mint(to[i], tokenIds[i], force[i], data[i]); } } - function mintIdRange( - address to, - uint256 startId, - uint256 amount, - bool force, - bytes memory data - ) public { - for(uint256 i = 0; i < amount; i++) { + function mintIdRange(address to, uint256 startId, uint256 amount, bool force, bytes memory data) public { + for (uint256 i = 0; i < amount; i++) { bytes32 tokenId = bytes32(startId + i); _mint(to, tokenId, force, data); } } - function burn( - bytes32 tokenId, - bytes memory data - ) public { + function burn(bytes32 tokenId, bytes memory data) public { _burn(tokenId, data); } - function burnBatch( - bytes32[] memory tokenIds, - bytes[] memory data - ) public { - require( - tokenIds.length == data.length, - "LSP8Mock: array length mismatch" - ); + function burnBatch(bytes32[] memory tokenIds, bytes[] memory data) public { + require(tokenIds.length == data.length, "LSP8Mock: array length mismatch"); - for(uint256 i = 0; i < tokenIds.length; i++) { + for (uint256 i = 0; i < tokenIds.length; i++) { _burn(tokenIds[i], data[i]); } } - function setData( - bytes32 tokenId, - bytes32 dataKey, - bytes memory dataValue - ) public { - _setData( dataKey, dataValue); + function setData(bytes32 tokenId, bytes32 dataKey, bytes memory dataValue) public { + _setData(dataKey, dataValue); } function transferBatch( @@ -85,16 +63,17 @@ contract LSP8Mock is LSP8IdentifiableDigitalAsset { bytes32[] memory tokenIds, bool[] memory force, bytes[] memory data - ) public override { + ) + public + override + { require( - from.length == to.length && - to.length == tokenIds.length && - tokenIds.length == force.length && - force.length == data.length, + from.length == to.length && to.length == tokenIds.length && tokenIds.length == force.length + && force.length == data.length, "LSP8Mock: array length mismatch" ); - for(uint256 i = 0; i < from.length; i++) { + for (uint256 i = 0; i < from.length; i++) { transfer(from[i], to[i], tokenIds[i], force[i], data[i]); } } From ad78c3bb24674a7a24580a10cedd682400481b6b Mon Sep 17 00:00:00 2001 From: Maxime Date: Thu, 31 Oct 2024 10:09:21 +0000 Subject: [PATCH 8/8] refactor: apply recommendations --- src/HypLSP8.sol | 22 +++++++++++++++++----- src/HypLSP8Collateral.sol | 11 ++++++----- test/HypLSP8.t.sol | 34 +++++++++------------------------- test/LSP8Mock.sol | 21 --------------------- 4 files changed, 32 insertions(+), 56 deletions(-) diff --git a/src/HypLSP8.sol b/src/HypLSP8.sol index de265ea..a225943 100644 --- a/src/HypLSP8.sol +++ b/src/HypLSP8.sol @@ -11,8 +11,8 @@ import { _LSP4_TOKEN_TYPE_TOKEN } from "@lukso/lsp4-contracts/contracts/LSP4Cons import { _LSP8_TOKENID_FORMAT_NUMBER } from "@lukso/lsp8-contracts/contracts/LSP8Constants.sol"; /** - * @title Hyperlane LSP8 Token Router that extends LSP8 with remote transfer functionality. - * @author Abacus Works + * @title LSP8 version of the Hyperlane ERC721 Token Router + * @dev https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/solidity/contracts/token/HypERC721.sol */ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { constructor(address _mailbox) TokenRouter(_mailbox) { } @@ -23,7 +23,18 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { * @param _name The name of the token. * @param _symbol The symbol of the token. */ - function initialize(uint256 _mintAmount, string memory _name, string memory _symbol) external initializer { + function initialize( + uint256 _mintAmount, + address _hook, + address _interchainSecurityModule, + address _owner, + string memory _name, + string memory _symbol + ) + external + initializer + { + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); address owner = msg.sender; _transferOwnership(owner); @@ -51,8 +62,9 @@ contract HypLSP8 is LSP8IdentifiableDigitalAssetInitAbstract, TokenRouter { * @inheritdoc TokenRouter */ function _transferFromSender(uint256 _tokenId) internal virtual override returns (bytes memory) { - require(tokenOwnerOf(bytes32(_tokenId)) == msg.sender, "!owner"); - _burn(bytes32(_tokenId), ""); + bytes32 tokenIdAsBytes32 = bytes32(_tokenId); + require(tokenOwnerOf(tokenIdAsBytes32) == msg.sender, "!owner"); + _burn(tokenIdAsBytes32, ""); return bytes(""); // no metadata } diff --git a/src/HypLSP8Collateral.sol b/src/HypLSP8Collateral.sol index bd155ef..6ec028b 100644 --- a/src/HypLSP8Collateral.sol +++ b/src/HypLSP8Collateral.sol @@ -5,21 +5,22 @@ import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRoute import { TokenMessage } from "@hyperlane-xyz/core/contracts/token/libs/TokenMessage.sol"; -import { ILSP8IdentifiableDigitalAsset } from "@lukso/lsp8-contracts/contracts/ILSP8IdentifiableDigitalAsset.sol"; +import { ILSP8IdentifiableDigitalAsset as ILSP8 } from + "@lukso/lsp8-contracts/contracts/ILSP8IdentifiableDigitalAsset.sol"; /** * @title Hyperlane LSP8 Token Collateral that wraps an existing LSP8 with remote transfer functionality. * @author Abacus Works */ contract HypLSP8Collateral is TokenRouter { - ILSP8IdentifiableDigitalAsset public immutable wrappedToken; + ILSP8 public immutable wrappedToken; /** * @notice Constructor * @param lsp8 Address of the token to keep as collateral */ constructor(address lsp8, address _mailbox) TokenRouter(_mailbox) { - wrappedToken = ILSP8IdentifiableDigitalAsset(lsp8); + wrappedToken = ILSP8(lsp8); } /** @@ -33,7 +34,7 @@ contract HypLSP8Collateral is TokenRouter { } function ownerOf(uint256 _tokenId) external view returns (address) { - return ILSP8IdentifiableDigitalAsset(wrappedToken).tokenOwnerOf(bytes32(_tokenId)); + return ILSP8(wrappedToken).tokenOwnerOf(bytes32(_tokenId)); } /** @@ -41,7 +42,7 @@ contract HypLSP8Collateral is TokenRouter { * @inheritdoc TokenRouter */ function balanceOf(address _account) external view override returns (uint256) { - return ILSP8IdentifiableDigitalAsset(wrappedToken).balanceOf(_account); + return ILSP8(wrappedToken).balanceOf(_account); } /** diff --git a/test/HypLSP8.t.sol b/test/HypLSP8.t.sol index f66f4a0..e406201 100644 --- a/test/HypLSP8.t.sol +++ b/test/HypLSP8.t.sol @@ -48,24 +48,13 @@ abstract contract HypTokenTest is Test { localMailbox.setDefaultHook(address(noopHook)); localMailbox.setRequiredHook(address(noopHook)); - remoteToken = new HypLSP8(address(remoteMailbox)); - - vm.prank(OWNER); - remoteToken.initialize(0, NAME, SYMBOL); - vm.deal(ALICE, 1 ether); } - function _deployRemoteToken(bool isCollateral) internal { - if (isCollateral) { - remoteToken = new HypLSP8(address(remoteMailbox)); - vm.prank(OWNER); - remoteToken.initialize(0, NAME, SYMBOL); - } else { - remoteToken = new HypLSP8(address(remoteMailbox)); - vm.prank(OWNER); - remoteToken.initialize(0, NAME, SYMBOL); - } + function _deployRemoteToken() internal { + remoteToken = new HypLSP8(address(remoteMailbox)); + vm.prank(OWNER); + remoteToken.initialize(0, address(noopHook), address(0), OWNER, NAME, SYMBOL); vm.prank(OWNER); remoteToken.enrollRemoteRouter(ORIGIN, address(localToken).addressToBytes32()); } @@ -98,11 +87,12 @@ contract HypLSP8Test is HypTokenTest { lsp8Token = HypLSP8(payable(address(localToken))); vm.prank(OWNER); - lsp8Token.initialize(INITIAL_SUPPLY, NAME, SYMBOL); + lsp8Token.initialize(INITIAL_SUPPLY, address(noopHook), address(0), OWNER, NAME, SYMBOL); vm.prank(OWNER); lsp8Token.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); + // Give accounts some ETH for gas vm.deal(OWNER, 1 ether); vm.deal(ALICE, 1 ether); vm.deal(BOB, 1 ether); @@ -111,12 +101,12 @@ contract HypLSP8Test is HypTokenTest { vm.prank(OWNER); lsp8Token.transfer(OWNER, ALICE, TOKEN_ID, true, ""); - _deployRemoteToken(false); + _deployRemoteToken(); } function testInitialize_revert_ifAlreadyInitialized() public { vm.expectRevert("Initializable: contract is already initialized"); - lsp8Token.initialize(INITIAL_SUPPLY, NAME, SYMBOL); + lsp8Token.initialize(INITIAL_SUPPLY, address(noopHook), address(0), OWNER, NAME, SYMBOL); } function testTotalSupply() public { @@ -181,17 +171,11 @@ contract HypLSP8CollateralTest is HypTokenTest { localPrimaryToken.transfer(OWNER, ALICE, TOKEN_ID, true, ""); vm.stopPrank(); - // Deploy remote token - remoteToken = new HypLSP8(address(remoteMailbox)); - vm.prank(OWNER); - remoteToken.initialize(0, NAME, SYMBOL); + _deployRemoteToken(); // Enroll routers for both chains vm.prank(OWNER); lsp8Collateral.enrollRemoteRouter(DESTINATION, address(remoteToken).addressToBytes32()); - - vm.prank(OWNER); - remoteToken.enrollRemoteRouter(ORIGIN, address(localToken).addressToBytes32()); } function testRemoteTransfer() public { diff --git a/test/LSP8Mock.sol b/test/LSP8Mock.sol index 9a5815a..8ca0998 100644 --- a/test/LSP8Mock.sol +++ b/test/LSP8Mock.sol @@ -57,27 +57,6 @@ contract LSP8Mock is LSP8IdentifiableDigitalAsset { _setData(dataKey, dataValue); } - function transferBatch( - address[] memory from, - address[] memory to, - bytes32[] memory tokenIds, - bool[] memory force, - bytes[] memory data - ) - public - override - { - require( - from.length == to.length && to.length == tokenIds.length && tokenIds.length == force.length - && force.length == data.length, - "LSP8Mock: array length mismatch" - ); - - for (uint256 i = 0; i < from.length; i++) { - transfer(from[i], to[i], tokenIds[i], force[i], data[i]); - } - } - // Override for testing purposes - allows easy token ID verification function tokenURI(bytes32 tokenId) public pure returns (string memory) { return "TEST-BASE-URI";