From 9b63dd32fa2f7b22df64d29e4bfddd2feca7c520 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 7 Oct 2024 15:26:26 +0100 Subject: [PATCH] feat: create `HypLSP7Collateral` + adjusted version of `HypERC20` when bridging an LSP7 from LUKSO to Ethereum --- src/HypERC20ForLSP7.sol | 31 ++++++++++++++++++++++ src/HypLSP7.sol | 24 ----------------- src/HypLSP7Collateral.sol | 53 +++++++++++++++++++++++++++++++++++++ src/TokenMessageForLSP7.sol | 44 ++++++++++++++++++++++++++++-- 4 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 src/HypERC20ForLSP7.sol create mode 100644 src/HypLSP7Collateral.sol diff --git a/src/HypERC20ForLSP7.sol b/src/HypERC20ForLSP7.sol new file mode 100644 index 0000000..bcfa371 --- /dev/null +++ b/src/HypERC20ForLSP7.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +// modules +import { HypERC20 } from "@hyperlane-xyz/core/contracts/token/HypERC20.sol"; +import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; + +// libraries +import { TypeCasts } from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; +import { TokenMessageForLSP7 } from "./TokenMessageForLSP7.sol"; + +contract HypERC20ForLSP7 is HypERC20 { + constructor(uint8 __decimals, address _mailbox) HypERC20(__decimals, _mailbox) { } + + /** + * @dev Mints tokens to recipient when router receives transfer message. + * @dev Emits `ReceivedTransferRemote` event on the destination chain. + * @param _origin The identifier of the origin chain. + * @param _message The encoded remote transfer message containing the recipient address and amount. + * + * @dev This function is overriden to extract the right params and calldata slices + * from a transfer message coming from LSP7, via the modified library `TokenMessageForLSP7`. + */ + function _handle(uint32 _origin, bytes32, bytes calldata _message) internal virtual override(TokenRouter) { + bytes32 recipient = TokenMessageForLSP7.recipient(_message); + uint256 amount = TokenMessageForLSP7.amount(_message); + bytes calldata metadata = TokenMessageForLSP7.metadata(_message); + _transferTo(TypeCasts.bytes32ToAddress(recipient), amount, metadata); + emit ReceivedTransferRemote(_origin, recipient, amount); + } +} diff --git a/src/HypLSP7.sol b/src/HypLSP7.sol index 3d9e6af..938dbde 100644 --- a/src/HypLSP7.sol +++ b/src/HypLSP7.sol @@ -6,9 +6,6 @@ import { LSP7DigitalAssetInitAbstract } from "@lukso/lsp7-contracts/contracts/LS import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -// libraries -import { TokenMessageForLSP7 } from "./TokenMessageForLSP7.sol"; - // constants import { _LSP4_TOKEN_TYPE_TOKEN } from "@lukso/lsp4-contracts/contracts/LSP4Constants.sol"; @@ -100,25 +97,4 @@ contract HypLSP7 is LSP7DigitalAssetInitAbstract, TokenRouter { { LSP7DigitalAssetInitAbstract._mint(_recipient, _amount, true, ""); } - - function _transferRemote( - uint32 _destination, - bytes32 _recipient, - uint256 _amountOrId, - uint256 _value, - bytes memory _hookMetadata, - address _hook - ) - internal - virtual - override(TokenRouter) - returns (bytes32 messageId) - { - bytes memory _tokenMetadata = _transferFromSender(_amountOrId); - bytes memory _tokenMessage = TokenMessageForLSP7.format(_recipient, _amountOrId, _tokenMetadata); - - messageId = _Router_dispatch(_destination, _value, _tokenMessage, _hookMetadata, _hook); - - emit SentTransferRemote(_destination, _recipient, _amountOrId); - } } diff --git a/src/HypLSP7Collateral.sol b/src/HypLSP7Collateral.sol new file mode 100644 index 0000000..bb84d8d --- /dev/null +++ b/src/HypLSP7Collateral.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.19; + +// Interfaces +import { ILSP7DigitalAsset as ILSP7 } from "@lukso/lsp7-contracts/contracts/ILSP7DigitalAsset.sol"; + +// Modules +import { TokenRouter } from "@hyperlane-xyz/core/contracts/token/libs/TokenRouter.sol"; + +contract HypLSP7Collateral is TokenRouter { + ILSP7 public immutable wrappedToken; + + /** + * @notice Constructor + * @param lsp7_ Address of the token to keep as collateral + */ + constructor(address lsp7_, address mailbox_) TokenRouter(mailbox_) { + wrappedToken = ILSP7(lsp7_); + } + + function initialize(address _hook, address _interchainSecurityModule, address _owner) public virtual initializer { + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); + } + + function balanceOf(address _account) external view override returns (uint256) { + return wrappedToken.balanceOf(_account); + } + + /** + * @dev Transfers `_amount` of `wrappedToken` from `msg.sender` to this contract. + * @inheritdoc TokenRouter + */ + function _transferFromSender(uint256 _amount) internal virtual override returns (bytes memory) { + wrappedToken.transfer(msg.sender, address(this), _amount, true, ""); + return bytes(""); // no metadata + } + + /** + * @dev Transfers `_amount` of `wrappedToken` from this contract to `_recipient`. + * @inheritdoc TokenRouter + */ + function _transferTo( + address _recipient, + uint256 _amount, + bytes calldata // no metadata + ) + internal + virtual + override + { + wrappedToken.transfer(address(this), _recipient, _amount, true, ""); + } +} diff --git a/src/TokenMessageForLSP7.sol b/src/TokenMessageForLSP7.sol index 5da1ebb..d4c83c2 100644 --- a/src/TokenMessageForLSP7.sol +++ b/src/TokenMessageForLSP7.sol @@ -4,13 +4,41 @@ pragma solidity >=0.8.0; /// @dev Adjusted version of the TokenMessage library from Hyperlane /// to extract parameters from the calldata of an LSP7 transfer /// according to the `transfer(address,address,uint256,bool,bytes)` signature. +/** + * @title TokenMessage library for LSP7 calldatas + * @author CJ42 + * @dev + * + * Example: for the following `transfer(...)` function call: + * + * from: 0x927aad446e3bf6eeb776387b3d7a89d8016fa54d (cj42) + * to: 0x345b918b9e06faa7b0e56bd71ba418f31f47fed4 (yamen) + * amount: 100000000000000000000 (= 100 tokens with 18 decimals) + * force: false + * data: 0x + * + * The calldata will look as follow: + * 0x760d9bba000000000000000000000000927aad446e3bf6eeb776387b3d7a89d8016fa54d000000000000000000000000345b918b9e06faa7b0e56bd71ba418f31f47fed40000000000000000000000000000000000000000000000056bc75e2d63100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000 + * + * 0x760d9bba -> bytes4 selector + * 000000000000000000000000927aad446e3bf6eeb776387b3d7a89d8016fa54d -> address `from` + * 000000000000000000000000345b918b9e06faa7b0e56bd71ba418f31f47fed4 -> address `to` + * 0000000000000000000000000000000000000000000000056bc75e2d63100000 -> uint256 `amount` + * 0000000000000000000000000000000000000000000000000000000000000000 -> bool `force` + * 00000000000000000000000000000000000000000000000000000000000000a0 -> offset of bytes `data` + * 0000000000000000000000000000000000000000000000000000000000000000 -> `data.length` = 0 + * + * Note: the offset of data is index starting from just after the bytes4 selector, where the data [length + value] is + * located in the calldata (0xa0 = 160). + */ library TokenMessageForLSP7 { function format(bytes32 _recipient, uint256 _amount, bytes memory _metadata) internal view returns (bytes memory) { return abi.encodePacked( - msg.sender, // TODO: which sender should be specified here? Should we add an extra parameter? + abi.encode(msg.sender), // TODO: which sender should be specified here? Should we add an + // extra parameter? _recipient, _amount, - true, // force param set to `true` by default + abi.encode(true), // force param set to `true` by default _metadata ); } @@ -22,4 +50,16 @@ library TokenMessageForLSP7 { function amount(bytes calldata message) internal pure returns (uint256) { return uint256(bytes32(message[64:96])); } + + function metadata(bytes calldata message) internal pure returns (bytes calldata) { + return message[128:]; + } } + +// 0x44c028fe +// 0000000000000000000000000000000000000000000000000000000000000000 +// 0000000000000000000000005b8b0e44d4719f8a328470dccd3746bfc73d6b14 +// 0000000000000000000000000000000000000000000000000000000000000000 +// 0000000000000000000000000000000000000000000000000000000000000080 +// 00000000000000000000000000000000000000000000000000000000000000c4 +// 760d9bba000000000000000000000000927aad446e3bf6eeb776387b3d7a89d8016fa54d000000000000000000000000345b918b9e06faa7b0e56bd71ba418f31f47fed40000000000000000000000000000000000000000000000056bc75e2d63100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000