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

Connectors: Implement wormhole bridge connector #15

Merged
merged 10 commits into from
Jun 26, 2023
Merged
53 changes: 53 additions & 0 deletions .github/workflows/ci-connectors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Connectors CI

env:
CI: true

on:
push:
branches: "*"
paths:
- packages/connectors/**
pull_request:
branches: "*"
paths:
- packages/connectors/**

jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up environment
uses: ./.github/actions/setup
- name: Lint
run: yarn workspace @mimic-fi/v3-connectors lint

test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up environment
uses: ./.github/actions/setup
- name: Build
run: yarn build
- name: Test
run: yarn workspace @mimic-fi/v3-connectors test

integration:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up environment
uses: ./.github/actions/setup
- name: Set up hardhat config
run: .github/scripts/setup-hardhat-config.sh ${{secrets.GOERLI_RPC}} ${{secrets.MUMBAI_RPC}} ${{secrets.MAINNET_RPC}} ${{secrets.POLYGON_RPC}} ${{secrets.OPTIMISM_RPC}} ${{secrets.ARBITRUM_RPC}} ${{secrets.GNOSIS_RPC}} ${{secrets.AVALANCHE_RPC}} ${{secrets.BSC_RPC}} ${{secrets.FANTOM_RPC}}
- name: Build
run: yarn build
- name: Test mainnet
run: yarn workspace @mimic-fi/v3-connectors test:mainnet
- name: Test polygon
run: yarn workspace @mimic-fi/v3-connectors test:polygon
2 changes: 2 additions & 0 deletions .github/workflows/ci-price-oracle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
uses: actions/checkout@v3
- name: Set up environment
uses: ./.github/actions/setup
- name: Set up hardhat config
run: .github/scripts/setup-hardhat-config.sh ${{secrets.GOERLI_RPC}} ${{secrets.MUMBAI_RPC}} ${{secrets.MAINNET_RPC}} ${{secrets.POLYGON_RPC}} ${{secrets.OPTIMISM_RPC}} ${{secrets.ARBITRUM_RPC}} ${{secrets.GNOSIS_RPC}} ${{secrets.AVALANCHE_RPC}} ${{secrets.BSC_RPC}} ${{secrets.FANTOM_RPC}}
- name: Build
run: yarn build
- name: Test mainnet
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
"packages/helpers",
"packages/registry",
"packages/fee-controller",
"packages/price-oracle",
"packages/authorizer",
"packages/price-oracle",
"packages/smart-vault",
"packages/tasks",
"packages/deployer"
"packages/deployer",
"packages/connectors"
]
}
}
9 changes: 9 additions & 0 deletions packages/connectors/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const ts = require('eslint-config-mimic/prettier')
const solidity = require('solhint-config-mimic/prettier')

module.exports = {
overrides: [
ts,
solidity
]
}
674 changes: 674 additions & 0 deletions packages/connectors/LICENSE

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@

pragma solidity >=0.8.0;

/**
* @title IPriceFeedProvider
* @dev Contract providing price feed references for (base, quote) token pairs
*/
interface IPriceFeedProvider {
/**
* @dev Tells the price feed address for (base, quote) pair. It returns the zero address if there is no one set.
* @param base Token to be rated
* @param quote Token used for the price rate
*/
function getPriceFeed(address base, address quote) external view returns (address);
interface IWormhole {
function transferTokensWithRelay(
address token,
uint256 amount,
uint256 toNativeTokenAmount,
uint16 targetChain,
bytes32 targetRecipientWallet
) external payable returns (uint64 messageSequence);

function relayerFee(uint16 chainId, address token) external view returns (uint256);
}
106 changes: 106 additions & 0 deletions packages/connectors/contracts/bridge/wormhole/WormholeConnector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';

import '@mimic-fi/v3-helpers/contracts/utils/ERC20Helpers.sol';

import './IWormhole.sol';

/**
* @title WormholeConnector
* @dev Interfaces with Wormhole to bridge tokens through CCTP
*/
contract WormholeConnector {
// List of Wormhole network IDs
uint16 private constant ETHEREUM_WORMHOLE_NETWORK_ID = 2;
uint16 private constant POLYGON_WORMHOLE_NETWORK_ID = 5;
uint16 private constant ARBITRUM_WORMHOLE_NETWORK_ID = 23;
uint16 private constant OPTIMISM_WORMHOLE_NETWORK_ID = 24;
uint16 private constant BSC_WORMHOLE_NETWORK_ID = 4;
uint16 private constant FANTOM_WORMHOLE_NETWORK_ID = 10;
uint16 private constant AVALANCHE_WORMHOLE_NETWORK_ID = 6;

// List of chain IDs supported by Wormhole
uint256 private constant ETHEREUM_ID = 1;
uint256 private constant POLYGON_ID = 137;
uint256 private constant ARBITRUM_ID = 42161;
uint256 private constant OPTIMISM_ID = 10;
uint256 private constant BSC_ID = 56;
uint256 private constant FANTOM_ID = 250;
uint256 private constant AVALANCHE_ID = 43114;

// Reference to the Wormhole's CircleRelayer contract of the source chain
IWormhole private immutable wormholeCircleRelayer;
lgalende marked this conversation as resolved.
Show resolved Hide resolved

/**
* @dev Creates a new Wormhole connector
* @param _wormholeCircleRelayer Address of the Wormhole's CircleRelayer contract for the source chain
*/
constructor(address _wormholeCircleRelayer) {
wormholeCircleRelayer = IWormhole(_wormholeCircleRelayer);
}

/**
* @dev Executes a bridge of assets using Wormhole's CircleRelayer integration
* @param chainId ID of the destination chain
* @param token Address of the token to be bridged
* @param amountIn Amount of tokens to be bridged
* @param minAmountOut Minimum amount of tokens willing to receive on the destination chain
* @param recipient Address that will receive the tokens on the destination chain
*/
function execute(uint256 chainId, address token, uint256 amountIn, uint256 minAmountOut, address recipient)
external
{
require(block.chainid != chainId, 'WORMHOLE_BRIDGE_SAME_CHAIN');
require(recipient != address(0), 'WORMHOLE_BRIDGE_RECIPIENT_ZERO');

uint16 wormholeNetworkId = _getWormholeNetworkId(chainId);
uint256 relayerFee = wormholeCircleRelayer.relayerFee(wormholeNetworkId, token);
require(minAmountOut <= amountIn - relayerFee, 'WORMHOLE_MIN_AMOUNT_OUT_TOO_BIG');

uint256 preBalanceIn = IERC20(token).balanceOf(address(this));

ERC20Helpers.approve(token, address(wormholeCircleRelayer), amountIn);
wormholeCircleRelayer.transferTokensWithRelay(
token,
amountIn,
0, // don't swap to native token
wormholeNetworkId,
bytes32(uint256(uint160(recipient))) // convert from address to bytes32
);

uint256 postBalanceIn = IERC20(token).balanceOf(address(this));
require(postBalanceIn >= preBalanceIn - amountIn, 'WORMHOLE_BAD_TOKEN_IN_BALANCE');
}

/**
* @dev Internal function to tell the Wormhole network ID based on a chain ID
lgalende marked this conversation as resolved.
Show resolved Hide resolved
* @param chainId ID of the chain being queried
* @return Wormhole network ID associated to the requested chain ID
*/
function _getWormholeNetworkId(uint256 chainId) private pure returns (uint16) {
lgalende marked this conversation as resolved.
Show resolved Hide resolved
if (chainId == ETHEREUM_ID) return ETHEREUM_WORMHOLE_NETWORK_ID;
else if (chainId == POLYGON_ID) return POLYGON_WORMHOLE_NETWORK_ID;
else if (chainId == ARBITRUM_ID) return ARBITRUM_WORMHOLE_NETWORK_ID;
else if (chainId == OPTIMISM_ID) return OPTIMISM_WORMHOLE_NETWORK_ID;
else if (chainId == BSC_ID) return BSC_WORMHOLE_NETWORK_ID;
else if (chainId == FANTOM_ID) return FANTOM_WORMHOLE_NETWORK_ID;
else if (chainId == AVALANCHE_ID) return AVALANCHE_WORMHOLE_NETWORK_ID;
else revert('WORMHOLE_UNKNOWN_CHAIN_ID');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

interface IAggregationExecutor {
/// @notice propagates information about original msg.sender and executes arbitrary data
function execute(address msgSender) external payable;
}

interface IOneInchV5AggregationRouter {
struct SwapDescription {
IERC20 srcToken;
IERC20 dstToken;
address payable srcReceiver;
address payable dstReceiver;
uint256 amount;
uint256 minReturnAmount;
uint256 flags;
}

/// @notice Performs a swap, delegating all calls encoded in `data` to `executor`. See tests for usage examples
/// @dev router keeps 1 wei of every token on the contract balance for gas optimisations reasons. This affects first swap of every token by leaving 1 wei on the contract.
/// @param executor Aggregation executor that executes calls described in `data`
/// @param desc Swap description
/// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
/// @param data Encoded calls that `caller` should execute in between of swaps
/// @return returnAmount Resulting token amount
/// @return spentAmount Source token amount
function swap(
IAggregationExecutor executor,
SwapDescription calldata desc,
bytes calldata permit,
bytes calldata data
) external payable returns (uint256 returnAmount, uint256 spentAmount);
}
68 changes: 68 additions & 0 deletions packages/connectors/contracts/swap/1inch/OneInchV5Connector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
import '@openzeppelin/contracts/utils/Address.sol';

import '@mimic-fi/v3-helpers/contracts/utils/ERC20Helpers.sol';

import './IOneInchV5AggregationRouter.sol';

/**
* @title OneInchV5Connector
* @dev Interfaces with 1inch V5 to swap tokens
*/
contract OneInchV5Connector {
// Reference to 1inch aggregation router v5
IOneInchV5AggregationRouter public immutable oneInchV5Router;

/**
* @dev Creates a new OneInchV5Connector contract
* @param _oneInchV5Router 1inch aggregation router v5 reference
*/
constructor(address _oneInchV5Router) {
oneInchV5Router = IOneInchV5AggregationRouter(_oneInchV5Router);
}

/**
* @dev Executes a token swap in 1Inch V5
* @param tokenIn Token to be sent
* @param tokenOut Token to received
* @param amountIn Amount of token in to be swapped
* @param minAmountOut Minimum amount of token out willing to receive
* @param data Calldata to be sent to the 1inch aggregation router
*/
function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes memory data)
external
returns (uint256 amountOut)
{
require(tokenIn != tokenOut, '1INCH_V5_SWAP_SAME_TOKEN');

uint256 preBalanceIn = IERC20(tokenIn).balanceOf(address(this));
uint256 preBalanceOut = IERC20(tokenOut).balanceOf(address(this));

ERC20Helpers.approve(tokenIn, address(oneInchV5Router), amountIn);
Address.functionCall(address(oneInchV5Router), data, '1INCH_V5_SWAP_FAILED');

uint256 postBalanceIn = IERC20(tokenIn).balanceOf(address(this));
require(postBalanceIn >= preBalanceIn - amountIn, '1INCH_V5_BAD_TOKEN_IN_BALANCE');

uint256 postBalanceOut = IERC20(tokenOut).balanceOf(address(this));
amountOut = postBalanceOut - preBalanceOut;
require(amountOut >= minAmountOut, '1INCH_V5_MIN_AMOUNT_OUT');
}
}
20 changes: 20 additions & 0 deletions packages/connectors/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
import '@mimic-fi/v3-helpers/dist/tests'
import 'hardhat-local-networks-config-plugin'

import { homedir } from 'os'
import path from 'path'

export default {
localNetworksConfig: path.join(homedir(), '/.hardhat/networks.mimic.json'),
solidity: {
version: '0.8.17',
settings: {
optimizer: {
enabled: true,
runs: 10000,
},
},
},
}
Loading