// (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 "../interfaces/IERC20SendAndCallReceiver.sol";
import {SafeERC20TransferFrom} from "../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 "./interface/IWAVAX.sol";
import {IUniswapFactory} from "./interface/IUniswapFactory.sol";
import {IUniswapPair} from "./interface/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);
}
}
}