Skip to content

Commit

Permalink
Sepolia Bridge (#34)
Browse files Browse the repository at this point in the history
* Initial setup for bridge and swap UniSwap Universal Router

* Further optimizations

* Remove unused dependencies

* Sepolia Bridge

---------

Co-authored-by: lz.wΔrlock() <[email protected]>
  • Loading branch information
sirarthurmoney and TRileySchwarz authored Mar 2, 2024
1 parent ce40c4a commit 9c80a2c
Show file tree
Hide file tree
Showing 24 changed files with 9,728 additions and 1,391 deletions.
3 changes: 2 additions & 1 deletion constants/chainIds.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"ethereum": 101,
"arbitrum": 110,
"optimism": 111,
"goerli-mainnet": 154
"goerli-mainnet": 154,
"sepolia-mainnet": 161
}
3 changes: 2 additions & 1 deletion constants/layerzeroEndpoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"ethereum": "0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675",
"arbitrum": "0x3c2269811836af69497E5F486A85D7316753cf62",
"optimism": "0x3c2269811836af69497E5F486A85D7316753cf62",
"goerli-mainnet": "0x9740FF91F1985D8d2B71494aE1A2f723bb3Ed9E4"
"goerli-mainnet": "0x9740FF91F1985D8d2B71494aE1A2f723bb3Ed9E4",
"sepolia-mainnet": "0x7cacBe439EaD55fa1c22790330b12835c6884a91"
}
9 changes: 8 additions & 1 deletion constants/oftArgs.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,12 @@
"minAmount": "0.0001",
"useMinAmount": true,
"contractName": "MinSendAmountOFT"
}
},
"sepolia-mainnet": {
"name": "Mainnet ETH",
"symbol": "METH",
"minAmount": "0.0001",
"useMinAmount": true,
"contractName": "MinSendAmountOFT"
}
}
3 changes: 2 additions & 1 deletion constants/weths.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"ethereum": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"arbitrum": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
"optimism": "0x4200000000000000000000000000000000000006",
"goerli-mainnet": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"
"goerli-mainnet": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
"sepolia-mainnet": "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14"
}
72 changes: 72 additions & 0 deletions contracts/SwapAndBridgeUniswapV3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
import "@layerzerolabs/solidity-examples/contracts/token/oft/IOFTCore.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract SwapAndBridgeUniswapV3 {
uint24 public constant poolFee = 3000; // 0.3%

IUniversalRouter public immutable universalRouter;
address public immutable weth;
IOFTCore public immutable oft;

constructor(address _weth, address _oft, address _universalRouter) {
require(_weth != address(0), "SwappableBridge: invalid WETH address");
require(_oft != address(0), "SwappableBridge: invalid OFT address");
require(_universalRouter != address(0), "SwappableBridge: invalid Universal Router address");

weth = _weth;
oft = IOFTCore(_oft);
universalRouter = IUniversalRouter(_universalRouter);
}

function swapAndBridge(uint amountIn, uint amountOutMin, uint16 dstChainId, address to, address payable refundAddress, address zroPaymentAddress, bytes calldata adapterParams) external payable {
require(to != address(0), "SwappableBridge: invalid to address");
require(msg.value >= amountIn, "SwappableBridge: not enough value sent");

// 1) commands
// 0x0b == WRAP_ETH ... https://docs.uniswap.org/contracts/universal-router/technical-reference#wrap_eth
// 0x00 == V3_SWAP_EXACT_IN... https://docs.uniswap.org/contracts/universal-router/technical-reference#v3_swap_exact_in
bytes memory commands = hex"0b00";

// 2) inputs
bytes[] memory inputs = new bytes[](2);
// For weth deposit
inputs[0] = abi.encode(
address(universalRouter), // recipient
amountIn // amount
);
// For token swap
inputs[1] = abi.encode(
address(this), // recipient
amountIn, // amount input
amountOutMin, // min amount output
// pathway(inputToken, poolFee, outputToken)
abi.encodePacked(weth, poolFee, address(oft)),
false
);

// 3) deadline
uint256 deadline = block.timestamp;

// Call execute on the universal Router
universalRouter.execute{value: amountIn}(commands, inputs, deadline);

// check balance after to see how many tokens we received
uint256 amountReceived = IERC20(address(oft)).balanceOf(address(this));
// redundant check to ensure we received enough tokens
require(amountReceived >= amountOutMin, "SwappableBridge: insufficient amount received");

oft.sendFrom{value: msg.value - amountIn}(
address(this),
dstChainId,
abi.encodePacked(to),
amountReceived,
refundAddress,
zroPaymentAddress,
adapterParams
);
}
}
86 changes: 61 additions & 25 deletions contracts/SwappableBridgeUniswapV3.sol
Original file line number Diff line number Diff line change
@@ -1,48 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
import "@layerzerolabs/solidity-examples/contracts/token/oft/IOFTCore.sol";
import "./IWETH.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./INativeOFT.sol";

contract SwappableBridgeUniswapV3 {
uint24 public constant poolFee = 3000; // 0.3%

IWETH public immutable weth;
INativeOFT public immutable nativeOft;
IUniversalRouter public immutable universalRouter;
address public immutable weth;
IOFTCore public immutable oft;
ISwapRouter public immutable swapRouter;

constructor(address _weth, address _oft, address _swapRouter) {
constructor(address _weth, address _oft, address _nativeOft, address _universalRouter) {
require(_weth != address(0), "SwappableBridge: invalid WETH address");
require(_oft != address(0), "SwappableBridge: invalid OFT address");
require(_swapRouter != address(0), "SwappableBridge: invalid Swap Router address");
require(_nativeOft != address(0), "SwappableBridge: invalid Native OFT address");
require(_universalRouter != address(0), "SwappableBridge: invalid Universal Router address");

weth = IWETH(_weth);
weth = _weth;
oft = IOFTCore(_oft);
swapRouter = ISwapRouter(_swapRouter);
nativeOft = INativeOFT(_nativeOft);
universalRouter = IUniversalRouter(_universalRouter);
}

function swapAndBridge(uint amountIn, uint amountOutMin, uint16 dstChainId, address to, address payable refundAddress, address zroPaymentAddress, bytes calldata adapterParams) external payable {
require(to != address(0), "SwappableBridge: invalid to address");
require(msg.value >= amountIn, "SwappableBridge: not enough value sent");

weth.deposit{value: amountIn}();
weth.approve(address(swapRouter), amountIn);

ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: address(weth),
tokenOut: address(oft),
fee: poolFee,
recipient: address(this),
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: amountOutMin,
sqrtPriceLimitX96: 0
});

uint amountOut = swapRouter.exactInputSingle(params);
oft.sendFrom{value: msg.value - amountIn}(address(this), dstChainId, abi.encodePacked(to), amountOut, refundAddress, zroPaymentAddress, adapterParams);
// 1) commands
// 0x0b == WRAP_ETH ... https://docs.uniswap.org/contracts/universal-router/technical-reference#wrap_eth
// 0x00 == V3_SWAP_EXACT_IN... https://docs.uniswap.org/contracts/universal-router/technical-reference#v3_swap_exact_in
bytes memory commands = hex"0b00";

// 2) inputs
bytes[] memory inputs = new bytes[](2);
// For weth deposit
inputs[0] = abi.encode(
address(universalRouter), // recipient
amountIn // amount
);
// For token swap
inputs[1] = abi.encode(
address(this), // recipient
amountIn, // amount input
amountOutMin, // min amount output
// pathway(inputToken, poolFee, outputToken)
abi.encodePacked(weth, poolFee, address(oft)),
false
);

// 3) deadline
uint256 deadline = block.timestamp;

// Call execute on the universal Router
universalRouter.execute{value: amountIn}(commands, inputs, deadline);

// check balance after to see how many tokens we received
uint256 amountReceived = IERC20(address(oft)).balanceOf(address(this));
// redundant check to ensure we received enough tokens
require(amountReceived >= amountOutMin, "SwappableBridge: insufficient amount received");

oft.sendFrom{value: msg.value - amountIn}(
address(this),
dstChainId,
abi.encodePacked(to),
amountReceived,
refundAddress,
zroPaymentAddress,
adapterParams
);
}

function bridge(uint amountIn, uint16 dstChainId, address to, address payable refundAddress, address zroPaymentAddress, bytes calldata adapterParams) external payable {
require(to != address(0), "SwappableBridge: invalid to address");
require(msg.value >= amountIn, "SwappableBridge: not enough value sent");

nativeOft.deposit{value: amountIn}();
nativeOft.sendFrom{value: msg.value - amountIn}(address(this), dstChainId, abi.encodePacked(to), amountIn, refundAddress, zroPaymentAddress, adapterParams);
}
}
37 changes: 37 additions & 0 deletions deploy/SwapAndBridgeUniswapV3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const WETHS = require("../constants/weths.json")
const OFT = {
"ethereum": "0xE71bDfE1Df69284f00EE185cf0d95d0c7680c0d4",
"sepolia-mainnet": "0x4f7A67464B5976d7547c860109e4432d50AfB38e",
"arbitrum": "0xE71bDfE1Df69284f00EE185cf0d95d0c7680c0d4",
"optimism": "0xE71bDfE1Df69284f00EE185cf0d95d0c7680c0d4",
}
const UNIVERSAL_ROUTER = {
"ethereum": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
"sepolia-mainnet": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
"arbitrum": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
"optimism": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
}

module.exports = async function ({ deployments, getNamedAccounts }) {
const { deploy } = deployments
const { deployer } = await getNamedAccounts()
console.log(`Deployer Address: ${deployer}`)

const wethAddress = WETHS[hre.network.name]
console.log(`[${hre.network.name}] WETH Address: ${wethAddress}`)

const oft = OFT[hre.network.name]
console.log(`[${hre.network.name}] OFT Address: ${oft}`)

const universalRouter = UNIVERSAL_ROUTER[hre.network.name]
console.log(`[${hre.network.name}] Universal Router Address: ${universalRouter}`)

await deploy("SwapAndBridgeUniswapV3", {
from: deployer,
args: [wethAddress, oft, universalRouter],
log: true,
waitConfirmations: 1,
})
}

module.exports.tags = ["SwapAndBridgeUniswapV3"]
28 changes: 21 additions & 7 deletions deploy/SwappableBridgeUniswapV3.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
const ROUTERS = require("../constants/swapRouters.json")
const WETHS = require("../constants/weths.json")
const OFT = {
"ethereum": "0xE71bDfE1Df69284f00EE185cf0d95d0c7680c0d4",
"sepolia-mainnet": "0x4f7A67464B5976d7547c860109e4432d50AfB38e",
}
const NATIVE_OFT = {
"ethereum": "0x4f7A67464B5976d7547c860109e4432d50AfB38e",
"sepolia-mainnet": "0x2e5221B0f855Be4ea5Cefffb8311EED0563B6e87",
}
const UNIVERSAL_ROUTER = {
"ethereum": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
"sepolia-mainnet": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD",
}

module.exports = async function ({ deployments, getNamedAccounts }) {
const { deploy } = deployments
const { deployer } = await getNamedAccounts()
console.log(`Deployer Address: ${deployer}`)

const routerAddress = ROUTERS[hre.network.name]
console.log(`[${hre.network.name}] Uniswap V3 SwapRouter Address: ${routerAddress}`)

const wethAddress = WETHS[hre.network.name]
console.log(`[${hre.network.name}] WETH Address: ${wethAddress}`)

const oft = await ethers.getContract("OFT")
console.log(`[${hre.network.name}] OFT Address: ${oft.address}`)
const oft = OFT[hre.network.name]
console.log(`[${hre.network.name}] OFT Address: ${oft}`)

const nativeOft = NATIVE_OFT[hre.network.name]
console.log(`[${hre.network.name}] Native OFT Address: ${nativeOft}`)

const universalRouter = UNIVERSAL_ROUTER[hre.network.name]
console.log(`[${hre.network.name}] Universal Router Address: ${universalRouter}`)

await deploy("SwappableBridgeUniswapV3", {
from: deployer,
args: [wethAddress, oft.address, routerAddress],
args: [wethAddress, oft, nativeOft, universalRouter],
log: true,
waitConfirmations: 1,
})
Expand Down
File renamed without changes.
Loading

0 comments on commit 9c80a2c

Please sign in to comment.