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

Group examples by topic #29

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,20 @@ anothersubnet = "http://localhost:9650/ext/bc/BASE58_BLOCKCHAIN_ID/rpc"
```

## Code Examples
- [0-send-receive](src/0-send-receive/_INSTRUCTIONS.md)
- [1-send-roundtrip](src/2-invoking-functions/_INSTRUCTIONS.md)
- [2-invoking-functions](src/0-send-receive/_INSTRUCTIONS.md)
- [3-registry](src/3-registry/_INSTRUCTIONS.md)
- [4-creating-contracts](src/4-creating-contracts/_INSTRUCTIONS.md)
- [x-erc721-bridge](src/x-erc721-bridge/Readme.md)

### Interchain Messaging
- [0a-send-receive](src/interchain-messaging/0a-send-receive/_INSTRUCTIONS.md)
- [0b-send-receive](src/interchain-messaging/0b-send-receive/_INSTRUCTIONS.md)
- [1-send-roundtrip](src/interchain-messaging/1-send-roundtrip/_INSTRUCTIONS.md)
- [2a-invoking-functions](src/interchain-messaging/2a-invoking-functions/_INSTRUCTIONS.md)
- [2b-invoking-functions](src/interchain-messaging/2b-invoking-functions/_INSTRUCTIONS.md)
- [2c-invoking-functions](src/interchain-messaging/2c-invoking-functions/_INSTRUCTIONS.md)
- [3-registry](src/interchain-messaging/3-registry/_INSTRUCTIONS.md)
- [4-incentivize-relayer](src/interchain-messaging/4-incentivize-relayer/_INSTRUCTIONS.md)

### Interchain Token Transfer
- [0-native-to-erc20-interchain-token-transfer](src/interchain-token-transfer/0-native-to-erc20-interchain-token-transfer/_INSTRUCTIONS.md)
- [1-erc20-to-native-interchain-token-transfer](src/interchain-token-transfer/1-erc20-to-native-interchain-token-transfer/_INSTRUCTIONS.md)
- [2-native-to-native-interchain-token-transfer](src/interchain-token-transfer/2-native-to-native-interchain-token-transfer/_INSTRUCTIONS.md)
- [3-erc20-to-erc20-interchain-token-transfer](src/interchain-token-transfer/3-erc20-to-erc20-interchain-token-transfer/_INSTRUCTIONS.md)
- [4-send-and-call](src/interchain-token-transfer/4-send-and-call/_INSTRUCTIONS.md)
File renamed without changes.
141 changes: 141 additions & 0 deletions src/interchain-token-transfer/4-send-and-call/DexERC20Wrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// (c) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.18;

import {IERC20SendAndCallReceiver} from "@avalanche-interchain-token-transfer/interfaces/IERC20SendAndCallReceiver.sol";
import {SafeERC20TransferFrom} from "@avalanche-interchain-token-transfer/utils/SafeERC20TransferFrom.sol";

import {SafeERC20} from "@openzeppelin/contracts@4.8.1/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts@4.8.1/token/ERC20/IERC20.sol";
import {Context} from "@openzeppelin/contracts@4.8.1/utils/Context.sol";

import {IWAVAX} from "./interfaces/IWAVAX.sol";
import {IUniswapFactory} from "./interfaces/IUniswapFactory.sol";
import {IUniswapPair} from "./interfaces/IUniswapPair.sol";

/**
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

contract DexERC20Wrapper is Context, IERC20SendAndCallReceiver {
using SafeERC20 for IERC20;

address public immutable WNATIVE;
address public immutable factory;

struct SwapOptions {
address tokenOut;
uint256 minAmountOut;
}

constructor(
address wrappedNativeAddress,
address dexFactoryAddress
) {
WNATIVE = wrappedNativeAddress;
factory = dexFactoryAddress;
}

event TokensReceived(
bytes32 indexed sourceBlockchainID,
address indexed originTokenTransferrerAddress,
address indexed originSenderAddress,
address token,
uint256 amount,
bytes payload
);

// To receive native when another contract called.
receive() external payable {}

function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256 amountOut) {
uint256 amountInWithFee = amountIn * 997;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 1e3 + amountInWithFee;
amountOut = numerator / denominator;
}

function query(
uint256 amountIn,
address tokenIn,
address tokenOut
) internal view returns (uint256 amountOut) {
if (tokenIn == tokenOut || amountIn == 0) {
return 0;
}
address pair = IUniswapFactory(factory).getPair(tokenIn, tokenOut);
if (pair == address(0)) {
return 0;
}
(uint256 r0, uint256 r1, ) = IUniswapPair(pair).getReserves();
(uint256 reserveIn, uint256 reserveOut) = tokenIn < tokenOut ? (r0, r1) : (r1, r0);
if (reserveIn > 0 && reserveOut > 0) {
amountOut = getAmountOut(amountIn, reserveIn, reserveOut);
}
}

function swap(
uint256 amountIn,
uint256 amountOut,
address tokenIn,
address tokenOut,
address to
) internal {
address pair = IUniswapFactory(factory).getPair(tokenIn, tokenOut);
(uint256 amount0Out, uint256 amount1Out) = (tokenIn < tokenOut)
? (uint256(0), amountOut) : (amountOut, uint256(0));
IERC20(tokenIn).safeTransfer(pair, amountIn);
IUniswapPair(pair).swap(amount0Out, amount1Out, to, new bytes(0));
}

function receiveTokens(
bytes32 sourceBlockchainID,
address originTokenTransferrerAddress,
address originSenderAddress,
address token,
uint256 amount,
bytes calldata payload
) external {
emit TokensReceived({
sourceBlockchainID: sourceBlockchainID,
originTokenTransferrerAddress: originTokenTransferrerAddress,
originSenderAddress: originSenderAddress,
token: token,
amount: amount,
payload: payload
});

require(payload.length > 0, "DexERC20Wrapper: empty payload");

IERC20 _token = IERC20(token);
// Receives teleported assets to be used for different purposes.
SafeERC20TransferFrom.safeTransferFrom(_token, _msgSender(), amount);

// Requests a quote from the Uniswap V2-like contract.
uint256 amountOut = query(amount, token, WNATIVE);
require(amountOut > 0, "DexERC20Wrapper: insufficient liquidity");

// Parses the payload of the message.
SwapOptions memory swapOptions = abi.decode(payload, (SwapOptions));
// Checks if the target swap price is still valid.
require(amountOut >= swapOptions.minAmountOut, "DexERC20Wrapper: slippage exceeded");

// Verifies if the desired tokenOut is a native or wrapped asset.
if (swapOptions.tokenOut == address(0)) {
swap(amount, amountOut, token, WNATIVE, address(this));
IWAVAX(WNATIVE).withdraw(amountOut);
payable(originSenderAddress).transfer(amountOut);
} else {
swap(amount, amountOut, token, WNATIVE, originSenderAddress);
}
}

}
82 changes: 82 additions & 0 deletions src/interchain-token-transfer/4-send-and-call/_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# DexERC20Wrapper - Avalanche Interchain Token Transfer (ICTT) sendAndCall Instructions

This guide explains how to deploy and interact with the `DexERC20Wrapper` contract on the Avalanche network for interchain token transfers and swaps. It assumes that you have already deployed your source and destination tokens and that there is a Uniswap V2-like decentralized exchange on the destination chain.

> **Note:** The source and destination chains involved in this process are Avalanche Layer 1s (L1s), previously referred to as subnets. Developers have the flexibility to deploy their own Avalanche L1s and easily transfer tokens and execute logic between these L1s.

## Overview

The process involves the following steps:
1. **Deploy Source and Destination Tokens**: Deploy tokens on both the source and destination Avalanche L1 chains.
2. **Wrap Uniswap V2-like Contracts**: The `DexERC20Wrapper` contract is deployed on the destination chain to interact with the existing Uniswap V2-like contract.
3. **Trigger `sendAndCall`**: Use the `sendAndCall` function on the source chain to transfer tokens to the destination chain. The `DexERC20Wrapper` contract will be triggered on the destination chain, executing the swap logic.

## Prerequisites

- **Source and Destination Tokens**: Ensure that your tokens are deployed on both the source and destination Avalanche L1 chains.
- **Uniswap V2-like Contracts**: The destination chain should have a Uniswap V2-like decentralized exchange with liquidity for the tokens you want to swap.

## Deploying the DexERC20Wrapper Contract

1. **Deploy the `DexERC20Wrapper` Contract**

Deploy the contract on the destination chain by providing the addresses for the wrapped native token (e.g., WAVAX) and the Uniswap V2 factory.

```solidity
constructor(
address wrappedNativeAddress,
address dexFactoryAddress
)
```

Example deployment command:

```bash
forge create --rpc-url <destination-network-rpc-url> --private-key <your-private-key> src/interchain-token-transfer/4-send-and-call/DexERC20Wrapper.sol:DexERC20Wrapper --constructor-args <wrapped-native-address> <uniswap-factory-address>
```

2. **Event Emissions**

The contract emits the `TokensReceived` event when tokens are received from the source chain. This event helps in tracking the token transfers and swap execution.

```solidity
event TokensReceived(
bytes32 indexed sourceBlockchainID,
address indexed originTokenTransferrerAddress,
address indexed originSenderAddress,
address token,
uint256 amount,
bytes payload
);
```

## Using the Contract for Interchain Transfers

### Step 1: Triggering `sendAndCall`

On the source chain, trigger the `sendAndCall` function to initiate the token transfer. Once the tokens are successfully sent, the `DexERC20Wrapper` contract on the destination chain will automatically be triggered.

The `receiveTokens` function in the `DexERC20Wrapper` contract will handle the received tokens, query the swap price, check for slippage, and execute the swap if conditions are met.

### Step 2: Swap Logic Execution

Upon receiving the tokens, the contract performs the following actions:

1. **Query Swap Price**: The contract queries the swap price using the internal `query` function and ensures that the output amount meets the minimum specified in the `payload` (slippage protection).
2. **Execute Swap**: The swap is executed on the Uniswap V2-like contract. If the output token is a native token (e.g., AVAX), the contract unwraps it and transfers the native token to the original sender.
3. **Slippage Check**: Slippage is handled by checking that the output amount is greater than or equal to the `minAmountOut` specified in the payload.

### Step 3: Handling Native Tokens

If the output token is a native asset, such as AVAX, the contract unwraps it and sends it to the original sender:

```solidity
IWAVAX(WNATIVE).withdraw(amountOut);
payable(originSenderAddress).transfer(amountOut);
```

For ERC20 tokens, the contract transfers the swapped tokens directly to the sender's address.

## Conclusion

The `DexERC20Wrapper` contract allows for seamless interchain token transfers and swaps on Avalanche L1s. By following the steps outlined in this guide, you can deploy and interact with the contract to transfer and swap tokens across Avalanche L1 chains, with slippage protection and support for both native and ERC20 tokens.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

interface IUniswapFactory {
function getPair(address tokenA, address tokenB) external view returns (address pair);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

interface IUniswapPair {
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);

function factory() external view returns (address);

function token0() external view returns (address);

function token1() external view returns (address);

function getReserves()
external
view
returns (
uint112 reserve0,
uint112 reserve1,
uint32 blockTimestampLast
);

function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

interface IWAVAX {
function withdraw(uint256 amount) external;

function deposit() external payable;
}