Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create HypLSP7Collateral, HypERC20 for LSP7 and improve documentation with Chart Mermaid diagrams #3

Merged
merged 15 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 4 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,71 +9,9 @@
[license]: https://opensource.org/licenses/MIT
[license-badge]: https://img.shields.io/badge/License-MIT-blue.svg

## Architecture & Workflow

The flow for bridging tokens is generally as follow:

- if the token is originally from ETH, the token is locked on ETHEREUM, and minted on LUKSO.
- if the token is originally from LUKSO, the token is burnt on LUKSO, minted on ETHEREUM.

### Ethereum -> LUKSO

![Ethereum to LUKSO bridge flow](./assets/flow-ethereum-lukso-hashi-bridge.png)


**on Ethereum chain**

1. User transfer ERC20 tokens to [`HypERC20Collateral`]. This locks the tokens in the collateral contract.
2. `HypERC20Collateral` contract call [`Mailbox`] to pass the message.
3. The `Mailbox` calls:
3.1. the default Hook (created by Hyperlane),
3.2. and the Hashi Hook (created by CCIA team).
4. Hashi Hook dispatch the token relaying message from `Yaho` contracts.

> In the architecture diagram above:
> - The `Yaho` contracts handle the dispatching and batching of messages across chains.
> - The `Yaru` contracts ensures that the messages are properly executed on the destination chain by calling relevant functions like `onMessage`.



**Off chain**

5. Hashi relayer (managed by CCIA team) listen for events from `Yaho` contracts and request the reporter contracts to relay token relaying message.
6. Hashi executor (managed by CCIA team) listen to event from each Hashi adapter contracts and call `Yaru.executeMessages`. **This step checks whether the Hashi adapters agree on a specify message id** (a threshold number of hash is stored), and set the message Id to verified status.
7. Validator (run by Hyperlane & LUKSO team) will sign the Merkle root when new dispatches happen in Mailbox.
8. Hyperlane relayer (run by Hyperlane team) relays the message by calling Mailbox.process().

**on LUKSO chain**

8. When [`Mailbox.process(...)`](https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/3d116132b87d36af9576d6b116f31a53d680db4a/solidity/contracts/Mailbox.sol#L188-L197) is called, it will:
8.1. check with Multisig ISM (includes Hashi ISM), whether the message is signed by validators & verified by Hashi ISM.
8.2. If so, it will mint [HypLSP7](./src/HypLSP7.sol) tokens to the receiver.


### LUKSO -> Ethereum

![LUKSO to Ethereum bridge flow](./assets/flow-lukso-ethereum-hashi-bridge.png)

**on LUKSO chain**

> _Step 1 to 3 needs to be confirmed_

1. User transfer LSP7 token to HypLSP7 contract and the tokens are burnt.
2. HypLSP7 contract calls `Mailbox` to pass the message.
3. `Mailbox` calls Default Hook (created by Hyperlane) and Hashi Hook (created by CCIA team).
4. Hashi Hook dispatch the token relaying message from Yaho contracts.

**Off chain**

4. Off chain process remains the same as before, _except there is no Light Client support for Hashi from LUKSO → Ethereum_.

**on Ethereum chain**

5. When `Mailbox.process()` is called:
5.1. it will check with Multisig ISM (includes Hashi ISM), whether the message is signed by validators & verified by Hashi ISM.
5.2. If so, it will unlock ERC20 token to the receiver on the Ethereum chain.

This repo is the LSP7 version of the [`HypERC20`] and [`HypERC20Collateral`] of `@hyperlane-xyz/core` package. They are used to bridge tokens between the Ethereum and LUKSO chains using the [Hashi Bridge](https://crosschain-alliance.gitbook.io/hashi).

For more details on the **architecture and bridging flow**, see the [**`docs/`**](./docs/README.md) folder.

### Examples of bridged tokens

Expand All @@ -82,11 +20,6 @@ The flow for bridging tokens is generally as follow:
- LUKSO -> ETH: https://explorer.hyperlane.xyz/message/0xf9c86a22e7b5584fc87a9d4ffc39f967a8745cd28b98ed2eaeb220c43996c4ca


### Relevant links & resources

- [Cross Chain Alliance - Hashi](https://crosschain-alliance.gitbook.io/hashi)
- [Hyperlane smart contracts monorepo](https://github.com/hyperlane-xyz/hyperlane-monorepo)

## Getting Started

This project is based on the Foundry template by PaulRBerg.
Expand All @@ -113,7 +46,6 @@ This is how to install dependencies:
Note that OpenZeppelin Contracts is pre-installed, so you can follow that as an example.



### Sensible Defaults

This template comes with a set of sensible default configurations for you to use. These defaults can be found in the
Expand All @@ -129,9 +61,6 @@ following files:
└── remappings.txt
```




## Usage

This is a list of the most frequently needed commands.
Expand Down Expand Up @@ -171,9 +100,7 @@ bun run test:coverage:report

### GitHub Actions

This repository uses pre-configured GitHub Actions. The contracts are linted and tested on every push and pull requests.

You can edit the CI script in [.github/workflows/ci.yml](./.github/workflows/ci.yml).
This repository uses pre-configured GitHub Actions. The contracts are linted and tested on every push and pull requests. You can edit the CI script in [.github/workflows/ci.yml](./.github/workflows/ci.yml).


## Foundry Resources
Expand All @@ -186,4 +113,5 @@ For example, if you're interested in exploring Foundry in more detail, you shoul


[`HypERC20Collateral`]: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/%40hyperlane-xyz/core%405.2.0/solidity/contracts/token/HypERC20Collateral.sol
[`HypERC20`]: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/%40hyperlane-xyz/core%405.2.0/solidity/contracts/token/HypERC20.sol
[`Mailbox`]: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/%40hyperlane-xyz/core%405.2.0/solidity/contracts/Mailbox.sol
Binary file modified assets/flow-ethereum-lukso-hashi-bridge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/flow-lukso-ethereum-hashi-bridge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bun.lockb
Binary file not shown.
158 changes: 158 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
## Bridging Flow Overview

The flow for bridging tokens is generally as follow. If the token is originally from:

### ETHEREUM -> LUKSO

**scenario 1:** the ERC20 token initially exists on Ethereum and was deployed there (_e.g: DAI, USDC, etc..._).

The ERC20 token is locked on ETHEREUM, an HypLSP7 token is minted on LUKSO.

```mermaid
%% Ethereum -> LUKSO - Bridge an existing ERC20 on Ethereum to LUKSO
%% example: DAI
graph TD
subgraph Source_Chain_Ethereum[Ethereum]
User[User 👤] -->|transfer ➡️| ERC20
ERC20 -->|lock 🔒| HypERC20Collateral
end
HypERC20Collateral .->|Bridging| HypLSP7
subgraph Destination_Chain_LUKSO[LUKSO]
HypLSP7 -->|mint ⛏️| End_User
End_User[User 👤]
end
```

**scenario 2:** the token was migrated from LUKSO to Ethereum and an HypERC20 token contract was created as a wrapper on the Ethereum side (_e.g: wrapped Chillwhale or wrapped FABS as HypERC20_).

The user burn the wrapped token `HypERC20` on Ethereum, and the tokens are unlocked on the LUKSO side and transferred to the user.

```mermaid
%% Ethereum -> LUKSO - LSP7 token that was initially bridged from LUKSO
%% example: Chillwhale
graph TD
subgraph Source_Chain_Ethereum[Ethereum]
User[User 👤] -->|burn 🔥| HypERC20
HypERC20[HypERC20]
end
HypERC20[HypERC20] .->|bridging| HypLSP7Collateral
subgraph Destination_Chain_LUKSO[LUKSO]
HypLSP7Collateral -->|unlock 🔓 + transfer| LSP7
end
```

### LUKSO -> ETHEREUM

- **scenario 3:** the LSP7 token was originally created and deployed on LUKSO (_e.g: Chillwhale, FABS, etc..._).

The user transfer the LSP7 token to its `HypLSP7Collateral` contract on LUKSO where it is locked. The HypERC20 token on Ethereum is then minted for the user.

```mermaid
graph TD
subgraph Source_Chain_LUKSO[LUKSO]
User[User 👤] -->|transfer ➡️| LSP7
LSP7 -->|transfer + lock 🔒| HypLSP7Collateral
end
HypLSP7Collateral .->|bridging| HypERC20

subgraph Destination_Chain_Ethereum[Ethereum]
HypERC20 -->|mint ⛏️| End_User
End_User[User 👤]
end
```

- **scenario 4:** an ERC20 token was bridged from Ethereum to LUKSO and we want to bridge back to Ethereum (_e.g: wrapped DAI as HypLSP7_).

This HypLSP7 token is burnt on LUKSO, on Ethereum it is unlocked.

```mermaid
graph TD
subgraph Source_Chain_LUKSO[LUKSO]
User[User 👤] -->|burn 🔥| HypLSP7
HypLSP7[HypLSP7]
end
HypLSP7 .->|bridging| HypERC20Collateral

subgraph Destination_Chain_Ethereum[Ethereum]
HypERC20Collateral -->|unlock 🔓| LSP7
LSP7 -->|transfer ➡️| End_User[User 👤]
end
```


## Detailed Architecture Diagrams

> **Notes:** in the architecture diagram below:
> - The `Yaho` contracts handle the dispatching and batching of messages across chains.
> - The `Yaru` contracts ensures that the messages are properly executed on the destination chain by calling relevant functions like `onMessage`.

### Ethereum -> LUKSO

> **Note:** This detailed diagram corresponds to the [**scenario 1**](#ethereum---lukso) above. Where an ERC20 token that initially exists on Ethereum (_e.g: DAI, USDC, etc..._) is bridged to LUKSO.

![Ethereum to LUKSO bridge flow](../assets/flow-ethereum-lukso-hashi-bridge.png)


**on Ethereum chain**

1. User transfer ERC20 tokens to [`HypERC20Collateral`]. This locks the tokens in the collateral contract.
2. `HypERC20Collateral` contract call [`Mailbox`] to pass the message.
3. The `Mailbox` calls:
- 3.1. the default Hook (created by Hyperlane),
- 3.2. and the Hashi Hook (created by CCIA team).
4. Hashi Hook dispatch the token relaying message from `Yaho` contracts.



**Off chain**

5. Hashi relayer (managed by CCIA team) listen for events from `Yaho` contracts and request the reporter contracts to relay token relaying message.
6. Hashi executor (managed by CCIA team) listen to event from each Hashi adapter contracts and call `Yaru.executeMessages`. **This step checks whether the Hashi adapters agree on a specify message id** (a threshold number of hash is stored), and set the message Id to verified status.
7. Validator (run by Hyperlane & LUKSO team) will sign the Merkle root when new dispatches happen in Mailbox.
8. Hyperlane relayer (run by Hyperlane team) relays the message by calling Mailbox.process().

**on LUKSO chain**

8. When [`Mailbox.process(...)`](https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/3d116132b87d36af9576d6b116f31a53d680db4a/solidity/contracts/Mailbox.sol#L188-L197) is called, it will:
- 8.1. check with Multisig ISM (includes Hashi ISM), whether the message is signed by validators & verified by Hashi ISM.
- 8.2. If so, it will mint [HypLSP7](./src/HypLSP7.sol) tokens to the receiver.


### LUKSO -> Ethereum

> **Note:** This detailed diagram corresponds to the [**scenario 4**](#lukso---ethereum) above. Where an ERC20 token was bridged from Ethereum to LUKSO and we want to bridge back to Ethereum (_e.g: wrapped DAI as HypLSP7_).

![LUKSO to Ethereum bridge flow](../assets/flow-lukso-ethereum-hashi-bridge.png)

**on LUKSO chain**

> _Step 1 to 3 needs to be confirmed_

1. User transfer LSP7 token to HypLSP7 contract and the tokens are burnt.
2. HypLSP7 contract calls `Mailbox` to pass the message.
3. `Mailbox` calls Default Hook (created by Hyperlane) and Hashi Hook (created by CCIA team).
4. Hashi Hook dispatch the token relaying message from Yaho contracts.

**Off chain**

4. Off chain process remains the same as before, _except there is no Light Client support for Hashi from LUKSO → Ethereum_.

**on Ethereum chain**

5. When `Mailbox.process()` is called:
- 5.1. it will check with Multisig ISM (includes Hashi ISM), whether the message is signed by validators & verified by Hashi ISM.
- 5.2. If so, it will unlock ERC20 token to the receiver on the Ethereum chain.






## Relevant links & resources

- [Cross Chain Alliance - Hashi](https://crosschain-alliance.gitbook.io/hashi)
- [Hyperlane smart contracts monorepo](https://github.com/hyperlane-xyz/hyperlane-monorepo)

[`HypERC20Collateral`]: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/%40hyperlane-xyz/core%405.2.0/solidity/contracts/token/HypERC20Collateral.sol
[`HypERC20`]: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/%40hyperlane-xyz/core%405.2.0/solidity/contracts/token/HypERC20.sol
[`Mailbox`]: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/%40hyperlane-xyz/core%405.2.0/solidity/contracts/Mailbox.sol
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"dependencies": {
"@erc725/smart-contracts-v8": "erc725-smart-contracts-v8-rc0.tgz",
"@hyperlane-xyz/core": "^5.0.0",
"@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"
},
Expand Down
31 changes: 31 additions & 0 deletions src/HypERC20ForLSP7.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
35 changes: 9 additions & 26 deletions src/HypLSP7.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ 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";

/**
* @title LSP7 version of the Hyperlane ERC20 Token Router
Expand All @@ -25,15 +25,19 @@ contract HypLSP7 is LSP7DigitalAssetInitAbstract, TokenRouter {
* @param _totalSupply The initial supply of the token.
* @param _name The name of the token.
* @param _symbol The symbol of the token.
*
* @dev The `LSP4TokenType` is hardcoded to type `Token` (= `0`) as all ERC20 tokens are of token type 0.
* This aims to keep the number of parameters consistent between hyperc20 and hypLSP7, so that the code of off-chain
* agents that call this function
* does not need to be modifed to add an extra parameter that would be irrelevant.
*/
function initialize(
uint256 _totalSupply,
string memory _name,
string memory _symbol,
address _hook,
address _interchainSecurityModule,
address _owner,
uint256 _lsp4TokenType
address _owner
)
external
initializer
Expand All @@ -43,7 +47,7 @@ contract HypLSP7 is LSP7DigitalAssetInitAbstract, TokenRouter {
name_: _name,
symbol_: _symbol,
newOwner_: _owner,
lsp4TokenType_: _lsp4TokenType,
lsp4TokenType_: _LSP4_TOKEN_TYPE_TOKEN,
isNonDivisible_: false // isNonDivisible set to `false` as will not be used anyway since decimals() is
// overriden
});
Expand Down Expand Up @@ -93,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);
}
}
Loading
Loading