Skip to content

Commit

Permalink
🗞️ Add mint and burn oft adapter example (#1120)
Browse files Browse the repository at this point in the history
  • Loading branch information
St0rmBr3w authored Dec 11, 2024
1 parent 60cc71c commit 3d61929
Show file tree
Hide file tree
Showing 33 changed files with 1,577 additions and 119 deletions.
15 changes: 15 additions & 0 deletions examples/mint-burn-oft-adapter/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-
# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \
# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-'
#
# Example environment configuration
#
# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-
# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \
# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-'

# By default, the examples support both mnemonic-based and private key-based authentication
#
# You don't need to set both of these values, just pick the one that you prefer and set that one
MNEMONIC=
PRIVATE_KEY=
10 changes: 10 additions & 0 deletions examples/mint-burn-oft-adapter/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
artifacts
cache
dist
node_modules
out
*.log
*.sol
*.yaml
*.lock
package-lock.json
10 changes: 10 additions & 0 deletions examples/mint-burn-oft-adapter/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require('@rushstack/eslint-patch/modern-module-resolution');

module.exports = {
extends: ['@layerzerolabs/eslint-config-next/recommended'],
rules: {
// @layerzerolabs/eslint-config-next defines rules for turborepo-based projects
// that are not relevant for this particular project
'turbo/no-undeclared-env-vars': 'off',
},
};
24 changes: 24 additions & 0 deletions examples/mint-burn-oft-adapter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts


# LayerZero specific files
.layerzero

# foundry test compilation files
out

# pnpm
pnpm-error.log

# Editor and OS files
.DS_Store
.idea
1 change: 1 addition & 0 deletions examples/mint-burn-oft-adapter/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.18.0
10 changes: 10 additions & 0 deletions examples/mint-burn-oft-adapter/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
artifacts/
cache/
dist/
node_modules/
out/
*.log
*ignore
*.yaml
*.lock
package-lock.json
3 changes: 3 additions & 0 deletions examples/mint-burn-oft-adapter/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
...require('@layerzerolabs/prettier-config-next'),
};
195 changes: 195 additions & 0 deletions examples/mint-burn-oft-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<p align="center">
<a href="https://layerzero.network">
<img alt="LayerZero" style="max-width: 500px" src="https://d3a2dpnnrypp5h.cloudfront.net/bridge-app/lz.png"/>
</a>
</p>
<h1 align="center">MintBurnOFTAdapter Example</h1>

<p align="center">
<a href="https://docs.layerzero.network/contracts/oft" style="color: #a77dff">Quickstart</a> | <a href="https://docs.layerzero.network/contracts/oapp-configuration" style="color: #a77dff">Configuration</a> | <a href="https://docs.layerzero.network/contracts/options" style="color: #a77dff">Message Execution Options</a> | <a href="https://docs.layerzero.network/contracts/endpoint-addresses" style="color: #a77dff">Endpoint Addresses</a>
</p>

<p align="center">This repository contains an example implementation of the MintBurnOFTAdapter, a variant of the OFTAdapter.sol standard from LayerZero. The purpose of this contract is to enable the deployment of more than one OFTAdapter within the mesh network, by utilziing an already deployed ERC20 token's external mint and burn methods on each chain.</p>

<p align="center">
<a href="https://layerzero.network" style="color: #a77dff">Homepage</a> | <a href="https://docs.layerzero.network/" style="color: #a77dff">Docs</a> | <a href="https://layerzero.network/developers" style="color: #a77dff">Developers</a>
</p>

---

- [Usage](#usage)
- [Developing Contracts](#developing-contracts)
- [Installing dependencies](#installing-dependencies)
- [Compiling your contracts](#compiling-your-contracts)
- [Running tests](#running-tests)
- [Deploying Contracts](#deploying-contracts)
- [What is an OFT Adapter?](#what-is-an-oft-adapter)
- [Key Features](#key-features)
- [Deployment Requirements](#deployment-requirements)
- [MintBurnOFTAdapter](#mintburnoftadapter)
- [Requirement](#requirement)
- [Contracts Structure](#contracts-structure)
- [`MinterBurner`](#minterburner)
- [`MintBurnOFTAdapter.sol`](#mintburnoftadaptersol)
- [Variables](#variables-1)
- [Functions](#functions-1)

## Usage

### OFTAdapter additional setup:

- In your `hardhat.config.ts` file, add the following configuration to the network you want to deploy the OFTAdapter to:
```typescript
// Replace `0x0` with the address of the ERC20 token you want to adapt to the OFT functionality.
oftAdapter: {
tokenAddress: '0x0',
}
```

### Developing Contracts

#### Installing dependencies

We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice):

```bash
pnpm install
```

#### Compiling your contracts

This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both:

```bash
pnpm compile
```

If you prefer one over the other, you can use the tooling-specific commands:

```bash
pnpm compile:forge
pnpm compile:hardhat
```

Or adjust the `package.json` to for example remove `forge` build:

```diff
- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat",
- "compile:forge": "forge build",
- "compile:hardhat": "hardhat compile",
+ "compile": "hardhat compile"
```

#### Running tests

Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both:

```bash
pnpm test
```

If you prefer one over the other, you can use the tooling-specific commands:

```bash
pnpm test:forge
pnpm test:hardhat
```

Or adjust the `package.json` to for example remove `hardhat` tests:

```diff
- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat",
- "test:forge": "forge test",
- "test:hardhat": "$npm_execpath hardhat test"
+ "test": "forge test"
```

### Deploying Contracts

Set up deployer wallet/account:

- Rename `.env.example` -> `.env`
- Choose your preferred means of setting up your deployer wallet/account:

```
MNEMONIC="test test test test test test test test test test test junk"
or...
PRIVATE_KEY="0xabc...def"
```

- Fund this address with the corresponding chain's native tokens you want to deploy to.

To deploy your contracts to your desired blockchains, run the following command in your project's folder:

```bash
npx hardhat lz:deploy
```

More information about available CLI arguments can be found using the `--help` flag:

```bash
npx hardhat lz:deploy --help
```

By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication.

<br></br>

<p align="center">
Join our community on <a href="https://discord-layerzero.netlify.app/discord" style="color: #a77dff">Discord</a> | Follow us on <a href="https://twitter.com/LayerZero_Labs" style="color: #a77dff">Twitter</a>
</p>

## What is an OFT Adapter?

OFT Adapter allows an existing token to expand to any supported chain as a native token with a unified global supply, inheriting all the features of the OFT Standard. This works as an intermediary contract that handles sending and receiving tokens that have already been deployed. Read more [here](https://docs.layerzero.network/v2/developers/evm/oft/adapter).

Ideally, when you want to convert an existing ERC20 token with its current fixed supply into an Omnichain token, you can use the OFTAdapter as a wrapper around that ERC20.

There are several ways to go about it since the base code of OFTAdapter keeps contract logic implementation up to the developer. Eg., the Adapter could be implemented in such a way that the original ERC20 is locked inside the Adapter on chain A and the OFT is minted on chain B.

## Key Features

- **Mint and Burn Access**: Enables the MintBurnOFTAdapter to interact with ERC20 tokens that have minting and burning capabilities. This is crucial for maintaining unified token supply across different blockchain networks in a decentralized manner.

- **Access Control Integration**: Ensures that only authorized entities (deployers or specific contracts) have the permissions to mint and burn tokens. This is managed through an access control or allowlist mechanism.

- **Multiple Adapter Deployments**: Supports the deployment of multiple instances of the MintBurnOFTAdapter, each configured with different token contracts and LayerZero endpoints, thus enhancing flexibility in cross-chain operations.

## Deployment Requirements

1. **ERC20 Token Access**: The deployer must ensure that the ERC20 token contract allows the MintBurnOFTAdapter to access its mint and burn methods. This typically requires configuring the ERC20 token's access control mechanisms to include the adapter's address in an allowlist.

2. **Adapter Deployment and Configuration**:
Deploy the MintBurnOFTAdapter with references to the ERC20 token, the LayerZero endpoint, and any relevant delegate addresses.
Add the address of the newly deployed MintBurnOFTAdapter to the ERC20 token's allowlist to enable minting and burning.

## MintBurnOFTAdapter

[`MyMintBurnOFTAdapter`](./contracts/MyMintBurnOFTAdapter.sol) is a variant of OFT Adapter that can use a token's external permissions to **burn** on chain A (source chain), as opposed to **lock**, and mint on chain B (destination chain).

## Requirement

The only requirement is that the base ERC20 must have an external or public `burn` and a `mint` function, and implement the `IMintableBurnable.sol` interface found in `./devtools/packages/oft-evm/interfaces/IMintableBurnable.sol`.

## Contracts Structure

### `MinterBurner`

This is a periphery contract for minting or burning tokens and executing arbitrary calls on the underlying ERC20.

### `MintBurnOFTAdapter.sol`

#### Variables

This is the actual OFT Adapter contract that maintains two constants: `innerToken` and `minterBurner`

- `innerToken`: underlying ERC20 implementation
- `minterBurner`: reference to the `IMintableBurnable` implementation that has the implementation of `burn` and `mint` functions

#### Functions

- `_debit`: Calls `burn` on `minterBurner` effectively burning tokens from sender's balance from source chain.
- `_credit`: Calls `mint` on `minterBurner`, effectively minting tokens to sender's balance on destination chain.

> [!IMPORTANT]
> The default `OFTAdapter` implementation assumes **lossless** transfers, ie. 1 token in = 1 token out. If the underlying ERC20 applies something like a transfer fee, the default will **not** work. A pre/post balance check will need to be added to calculate the `amountReceivedLD`.
25 changes: 25 additions & 0 deletions examples/mint-burn-oft-adapter/contracts/MyMintBurnOFTAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { MintBurnOFTAdapter } from "@layerzerolabs/oft-evm/contracts/MintBurnOFTAdapter.sol";
import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol";

/**
* @title MintBurnOFTAdapter Contract
* @dev MintBurnOFTAdapter is a contract that adapts an ERC-20 token with external mint and burn logic to the OFT functionality.
*
* @dev For existing ERC20 tokens with exposed mint and burn permissions, this can be used to convert the token to crosschain compatibility.
* @dev Unlike the vanilla OFT Adapter, multiple of these can exist for a given global mesh.
* @dev WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, ie. 1 token in, 1 token out.
* IF the 'innerToken' applies something like a transfer fee, the default will NOT work...
* a pre/post balance check will need to be done to calculate the amountSentLD/amountReceivedLD.
*/
contract MyMintBurnOFTAdapter is MintBurnOFTAdapter {
constructor(
address _token,
IMintableBurnable _minterBurner,
address _lzEndpoint,
address _delegate
) MintBurnOFTAdapter(_token, _minterBurner, _lzEndpoint, _delegate) Ownable(_delegate) {}
}
14 changes: 14 additions & 0 deletions examples/mint-burn-oft-adapter/contracts/MyOFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol";

contract MyOFT is OFT {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol";

/**
* @title MintBurnERC20Mock
*
* @dev WARNING: This contract is for testing purposes only.
* In a production scenario, the `mint` and `burn` methods
* should be guarded by appropriate access control mechanisms.
*/
contract MintBurnERC20Mock is ERC20, IMintableBurnable {
/// @notice Constructor to initialize the ERC20 token with a name and symbol.
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

/**
* @notice Burns a specific amount of tokens from a given address.
*
* @dev WARNING: In production, this function should have access control.
*
* @param _from The address from which tokens will be burned.
* @param _amount The amount of tokens to burn.
*
* @return A boolean indicating the success of the burn operation.
*/
function burn(address _from, uint256 _amount) external returns (bool) {
_burn(_from, _amount);
return true;
}

/**
* @notice Mints a specific amount of tokens to a given address.
*
* @dev WARNING: In production, this function should have access control.
*
* @param _to The address to which tokens will be minted.
* @param _amount The amount of tokens to mint.
*
* @return A boolean indicating the success of the mint operation.
*/
function mint(address _to, uint256 _amount) external returns (bool) {
_mint(_to, _amount);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import { MyMintBurnOFTAdapter } from "../MyMintBurnOFTAdapter.sol";

import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol";

// @dev WARNING: This is for testing purposes only
contract MyMintBurnOFTAdapterMock is MyMintBurnOFTAdapter {
constructor(
address _token,
IMintableBurnable _minterBurner,
address _lzEndpoint,
address _delegate
) MyMintBurnOFTAdapter(_token, _minterBurner, _lzEndpoint, _delegate) {}
}
Loading

0 comments on commit 3d61929

Please sign in to comment.