-
Notifications
You must be signed in to change notification settings - Fork 253
DEVREL-911 DEVREL-522 ποΈ Add composer swap examples #1097
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
base: main
Are you sure you want to change the base?
Changes from 34 commits
f444b7c
e95cd88
3f93284
4a32f13
b9cb966
d3ab75a
9761620
119b75d
8b19386
a2009fa
2016957
010c95a
50898b8
54230f8
5ef8557
818b2f1
e84ea84
6c6fb79
589ba19
86b1820
56bcad7
4c11370
483be2d
12cfb36
ea2d70f
2137375
cf43a00
d39b814
a42a10c
b91bfb6
618decb
34df443
ddd1155
8ddeba5
03ddcb9
447f352
3ed94f4
ff80a12
b9496d8
d6c0bfc
2d01144
b90bbb5
739ea88
579c838
b57ae3b
392bf10
7947ba9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "create-lz-oapp": patch | ||
| --- | ||
|
|
||
| Adds OFT composer library |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't really need this file now |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "create-lz-oapp": patch | ||
| --- | ||
|
|
||
| Adds lzCompose 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= |
| 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 |
| 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', | ||
| }, | ||
| }; |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| v18.18.0 |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module.exports = { | ||
| ...require('@layerzerolabs/prettier-config-next'), | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| <p align="center"> | ||
| <a href="https://layerzero.network"> | ||
| <img alt="LayerZero" style="width: 400px" src="https://docs.layerzero.network/img/LayerZero_Logo_White.svg"/> | ||
| </a> | ||
| </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> | ||
|
|
||
| <h1 align="center">LayerZero OFT Composer Library</h1> | ||
|
||
|
|
||
| <p align="center"> | ||
| <a href="https://docs.layerzero.network/v2/developers/evm/oft/quickstart" 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/v2/developers/evm/composer/overview" style="color: #a77dff">Composer Overview</a> | ||
| </p> | ||
|
|
||
| <p align="center"> | ||
| A Composer library to integrate LayerZero composer contracts with the Omnichain Fungible Token (OFT) standard. | ||
| </p> | ||
|
|
||
| - [What is an Omnichain Fungible Token?](#what-is-an-omnichain-fungible-token) | ||
| - [Using Composer with OFTs](#using-composer-with-ofts) | ||
| - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) | ||
| - [Developing & Deploying Contracts](#developing-contracts) | ||
| - [Connecting Contracts](#connecting-contracts) | ||
|
|
||
| ## What is an Omnichain Fungible Token? | ||
|
|
||
| The Omnichain Fungible Token (OFT) standard extends the ERC20 interface to enable seamless cross-chain token transfers without the need for asset wrapping or intermediary chains. By combining LayerZeroβs OApp Contract Standard with ERC20βs `_burn` and `_mint` methods, OFTs maintain a unified token supply across multiple blockchains. | ||
|
||
|
|
||
| <img alt="LayerZero" src="https://docs.layerzero.network/assets/images/oft_mechanism_light-922b88c364b5156e26edc6def94069f1.jpg#gh-light-mode-only"/> | ||
|
|
||
| Learn more about OFTs in the [OFT Quickstart](https://docs.layerzero.network/v2/developers/evm/oft/quickstart). | ||
|
|
||
| ## Using Composer with OFTs | ||
|
|
||
| This repository is not only a template for OFTs, itβs a fully featured **composer library** that empowers you to build composable, cross-chain applications using LayerZeroβs composer contracts in combination with the OFT standard. | ||
|
|
||
| For example, our repository includes an example contract (see [UniswapV3Composer.sol](./contracts/UniswapV3Composer.sol)) that demonstrates how to: | ||
|
|
||
| - Receive cross-chain messages via LayerZero. | ||
| - Decode composable messages (using `OFTComposeMsgCodec`) to extract parameters. | ||
| - Execute token swaps on Uniswap V3 after an OFT transfer. | ||
| - Gracefully handle failures by refunding tokens. | ||
|
|
||
| This composability approach lets you extend basic omnichain token transfers with additional logicβsuch as token swaps, lending, or other decentralized finance features. For more information on composability, visit the [What is Composability?](https://docs.layerzero.network/v2/concepts/applications/composer-standard) documentation and the [EVM Composer Overview](https://docs.layerzero.network/v2/developers/evm/composer/overview). | ||
|
|
||
| ## LayerZero Hardhat Helper Tasks | ||
|
|
||
| LayerZero Devtools offers several helper tasks to deploy, configure, connect, and wire your OFT and composer contracts across multiple chains. These tasks streamline operations like: | ||
|
|
||
| - **Deploying Contracts:** | ||
|
|
||
| ```bash | ||
| npx hardhat lz:deploy | ||
| ``` | ||
|
|
||
| Deploy your contracts to networks specified in your `hardhat.config.ts`. | ||
|
|
||
| - **Initializing Configuration:** | ||
|
|
||
| ```bash | ||
| npx hardhat lz:oapp:config:init --contract-name YOUR_CONTRACT_NAME --oapp-config FILE_NAME | ||
| ``` | ||
|
|
||
| Generate a default `layerzero.config.ts` file for setting up cross-chain pathways. | ||
|
|
||
| - **Wiring Contracts:** | ||
|
|
||
| ```bash | ||
| npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE | ||
| ``` | ||
|
|
||
| Connect your deployed contracts by executing the necessary configuration functions. | ||
|
|
||
| - **Viewing Current Configurations:** | ||
| ```bash | ||
| npx hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG | ||
| ``` | ||
| Review the active, custom, and default configurations for each pathway. | ||
|
|
||
| For more details, refer to the [LayerZero Hardhat Helper Tasks Documentation](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying). | ||
|
|
||
| ## Developing Contracts | ||
|
|
||
| ### Installing dependencies | ||
|
|
||
| We recommend using `pnpm` (or your preferred package manager): | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| ### Compiling your contracts | ||
|
|
||
| This project supports both Hardhat and Forge compilations: | ||
|
|
||
| ```bash | ||
| pnpm compile | ||
| ``` | ||
|
|
||
| To compile with a specific tool: | ||
|
|
||
| ```bash | ||
| pnpm compile:hardhat | ||
| pnpm compile:forge | ||
| ``` | ||
|
|
||
| ### Running tests | ||
|
|
||
| Both Hardhat and Forge tests are supported: | ||
|
|
||
| ```bash | ||
| pnpm test | ||
| ``` | ||
|
|
||
| Or run specific tests: | ||
|
|
||
| ```bash | ||
| pnpm test:hardhat | ||
| pnpm test:forge | ||
| ``` | ||
|
|
||
| ## Deploying Contracts | ||
|
|
||
| 1. **Set Up Deployer Wallet:** | ||
|
|
||
| - Rename `.env.example` to `.env`. | ||
| - Configure your mnemonic or private key. | ||
|
|
||
| 2. **Fund Your Account:** | ||
| Ensure your deployer wallet is funded with the appropriate native tokens. | ||
|
|
||
| 3. **Deploy Contracts:** | ||
| ```bash | ||
| npx hardhat lz:deploy | ||
| ``` | ||
| Use the `--help` flag for additional CLI options. | ||
|
|
||
| ## Connecting Contracts | ||
|
|
||
| 1. **Configure Connections:** | ||
| Generate and customize your `layerzero.config.ts` file: | ||
|
|
||
| ```bash | ||
| npx hardhat lz:oapp:config:init --contract-name [YOUR_CONTRACT_NAME] --oapp-config [CONFIG_NAME] | ||
| ``` | ||
|
|
||
| 2. **Define Network Pathways:** | ||
| Specify contracts and their interconnections (e.g., Ethereum <--> Arbitrum) in your configuration file. | ||
|
|
||
| 3. **Apply Configuration:** | ||
| Connect your deployed contracts by running: | ||
| ```bash | ||
| npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts | ||
| ``` | ||
|
|
||
| Join our community on [Discord](https://discord-layerzero.netlify.app/discord) or follow us on [Twitter](https://twitter.com/LayerZero_Labs) for updates. | ||
|
|
||
| --- | ||
|
|
||
| By following these steps, you leverage LayerZeroβs composer library to build and deploy innovative, composable cross-chain applications powered by the OFT standard. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.19; | ||
|
|
||
| import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
|
||
| import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol"; | ||
| import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol"; | ||
| import { IOAppCore } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol"; | ||
| import { IStargate } from "@stargatefinance/stg-evm-v2/src/interfaces/IStargate.sol"; | ||
|
|
||
| import { IAaveV3Composer } from "./IAaveV3Composer.sol"; | ||
| import { IPool } from "./IAaveV3Pool.sol"; | ||
|
|
||
| /** | ||
| * @title AaveV3Composer | ||
| * | ||
| * @notice Supplies the received tokens into Aave V3 liquidity pools after an lzCompose call. | ||
| * | ||
| * @dev The contract enforces strict authentication, decodes lzCompose payloads, and either supplies tokens into Aave | ||
| * or refunds the recipient if the supply reverts. | ||
| */ | ||
| contract AaveV3Composer is ILayerZeroComposer, IAaveV3Composer { | ||
| using SafeERC20 for IERC20; | ||
|
|
||
| /// @notice Aave V3 pool that receives supplied liquidity. | ||
| IPool public immutable AAVE; | ||
|
|
||
| /// @notice LayerZero Endpoint trusted to invoke `lzCompose`. | ||
| address public immutable ENDPOINT; | ||
|
|
||
| /// @notice Stargate OFT that is authorized to trigger Aave supplies on this chain. | ||
| address public immutable STARGATE; | ||
|
|
||
| /// @notice Underlying ERC20 token that backs the trusted Stargate OFT. | ||
| address public immutable TOKEN_IN; | ||
|
|
||
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| // 0. Setup & Constructor | ||
| // β’ Validates constructor inputs | ||
| // β’ Caches protocol addresses | ||
| // β’ Grants a one-time approval for Aave supplies | ||
| // | ||
| // β³ Docs: https://docs.layerzero.network/v2/developers/evm/composer/overview | ||
| // https://docs.layerzero.network/v2/developers/evm/oft/quickstart#send-tokens--call-composer | ||
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
|
|
||
| /** | ||
| * @notice Deploys the composer and connects it to Stargate and Aave pools. | ||
| * | ||
| * @dev Assuming the Stargate contract is already configured with the correct LayerZero Endpoint. | ||
| * | ||
| * A **one-time `maxApprove`** is granted to the Aave Pool because: | ||
| * 1. Funds only arrive via `lzReceive` β `lzCompose` from the trusted Stargate Pool. | ||
| * 2. The pool can only transfer what the composeMsg allows. | ||
| * 3. Saves ~5k gas on every compose execution. | ||
| * | ||
| * @param _aavePool Address of the target Aave V3 pool. | ||
| * @param _stargatePool StargatePool expected to receive the supplied tokens. | ||
| */ | ||
| constructor(address _aavePool, address _stargatePool) { | ||
| if (_aavePool == address(0)) revert InvalidAavePool(); | ||
| if (_stargatePool == address(0)) revert InvalidStargatePool(); | ||
|
|
||
| // Initialize the Aave pool. | ||
| AAVE = IPool(_aavePool); | ||
|
|
||
| // Initialize the Stargate Pool. | ||
| STARGATE = _stargatePool; | ||
|
|
||
| // Gran the endpoint from the StargatePool. | ||
| ENDPOINT = address(IOAppCore(STARGATE).endpoint()); | ||
St0rmBr3w marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Grab the underlying token from the StargatePool. | ||
| TOKEN_IN = IStargate(_stargatePool).token(); | ||
St0rmBr3w marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Grant a one-time unlimited allowance so Aave can pull funds during supply. | ||
| IERC20(TOKEN_IN).approve(address(AAVE), type(uint256).max); | ||
| } | ||
|
|
||
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| // 1. Compose Logic (`lzCompose`) | ||
| // Called by the LayerZero Endpoint after a user sends tokens cross-chain | ||
| // with a compose message. Decodes the message and performs a Aave V3 supply | ||
| // on behalf of the original sender. | ||
|
|
||
| // Steps: | ||
| // 1. Authenticity checks (trusted Stargate Pool & Endpoint) | ||
| // 2. Decode recipient address and amount from `_message` | ||
| // 3. Try to execute supply on behalf of the recipient β emit `SupplyExecuted` | ||
| // β’ On failure, refund tokens β emit `SupplyFailedAndRefunded` | ||
| // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
|
|
||
| /** | ||
| * @notice Consumes composed messages and supplies the received tokens into the Aave V3 pool. | ||
| * @dev `_message` is encoded by the OFT.send() caller on the source chain via | ||
| * `OFTComposeMsgCodec.encode()` and has the following layout: | ||
| * | ||
| * ``` | ||
| * | srcNonce (uint64) | srcEid (uint32) | amountLD (uint128) | | ||
| * | composeFrom (bytes32) | composeMsg (bytes) | | ||
| * ``` | ||
| * | ||
| * `composeMsg` (last field) is expected to be: | ||
| * `abi.encode(address onBehalfOf)`. | ||
| * | ||
| * | ||
| * @param _oft Address of the originating OFT; must equal the trusted `stargate`. | ||
| * @dev _guid Message hash (unused, but kept for future extensibility). | ||
| * @param _message ABI-encoded compose payload containing recipient address. | ||
| * @dev _executor Executor that relayed the message (unused). | ||
| * @dev _extraData Extra data from executor (unused). | ||
| */ | ||
| function lzCompose( | ||
| address _oft, | ||
| bytes32 /* _guid */, | ||
| bytes calldata _message, | ||
| address /* _executor */, | ||
| bytes calldata /* _extraData */ | ||
| ) external payable { | ||
| // Step 1οΈ: Authenticate call logic source. | ||
| if (_oft != STARGATE) revert UnauthorizedStargatePool(); | ||
| if (msg.sender != ENDPOINT) revert UnauthorizedEndpoint(); | ||
|
|
||
| // Step 2οΈ: Decode the recipient address and amount from the message. | ||
| address _to = abi.decode(OFTComposeMsgCodec.composeMsg(_message), (address)); | ||
| uint256 amountLD = OFTComposeMsgCodec.amountLD(_message); | ||
|
|
||
| // Step 3: Execute the supply or refund to target recipient. | ||
| try AAVE.supply(TOKEN_IN, amountLD, _to, 0) { | ||
| // 0 is the referral code | ||
| emit SupplyExecuted(_to, amountLD); | ||
| } catch { | ||
| IERC20(TOKEN_IN).safeTransfer(_to, amountLD); | ||
| emit SupplyFailedAndRefunded(_to, amountLD); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also not a fan of calling it "oft composer library"