-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1026 from yingjingyang/main
add chainlink CCIP && Celer bridge
- Loading branch information
Showing
61 changed files
with
7,193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
PRIVATE_KEY=xxxx | ||
PRIVATE_KEY1=yyyy | ||
PRIVATE_KEY2=yyyy | ||
INFURA_ID=yyyy | ||
PROJECT_ID=yyyy | ||
TARGET_ACCOUNT=yyyy | ||
API_KEY=yyyy | ||
EHTERSCAN_KEY=yyyy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
node_modules | ||
.env | ||
coverage | ||
coverage.json | ||
typechain | ||
|
||
#Hardhat files | ||
cache | ||
artifacts | ||
|
||
**/deployment.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Celer Bridge 跨链 | ||
中文 / [English](./README.md) | ||
|
||
## 概览 | ||
cBridge 引入了最佳的跨链代币桥接体验,为用户提供了深度流动性,为 cBridge 节点运营商和不想运营 cBridge 节点的流动性提供者提供了高效且易于使用的流动性管理,以及新的令人兴奋的面向开发者的功能,如用于跨链 DEX 和 NFT 等场景的通用消息桥接。 | ||
|
||
cBridge 有两种 bridge 方式,一种是 Pool-Based ,一种是 Canonical Mapping。 | ||
Pool-Based 就是在 A 链和 B 链之间各自锁定相同的 token,比如 USDT。当用户需要从 A 链跨到 B 链的时候,先会把 USDT 转入 A 链这边的 Vault 中,然后在 B 链这边把 USDT transfer 给用户。这个情况下,就需要在 A 链和 B 链上各自建立 pool 来完成这个操作,当 pool 中的资金不足时,就会出现无法 bridge 的情况。这种模式被称为 lock/unlock。 | ||
Canonical Mapping 对应的就是 lock/mint。用户 bridge 的时候,会把 USDT lock 到 A 链这边的 vault,然后在 B 链这边 mint 出对应的资产给用户。反过来,当用户想把 B 链上的 USDT bridge 会 A 链的时候,会把 B 链上的 USDT burn 掉,然后在 A 链这边把 USDT 从 vault 再 transfer 给用户。 | ||
|
||
本测试将以 OP mainnet 和 Polygon mainnet 进行测试 | ||
|
||
## 测试环境要求 | ||
node 版本需要为 v18.17.0, 可以使用 nvm 切换当前 node 版本 | ||
|
||
## 准备测试环境 | ||
- 安装依赖 | ||
``` | ||
npm install | ||
``` | ||
|
||
- 配置环境 | ||
``` | ||
cp .env.example .env | ||
## 之后在 .env 中配置具体的私钥 | ||
``` | ||
|
||
## 执行跨链 | ||
- 以流动性池方式进行跨链 | ||
``` | ||
npx hardhat run scripts/1-poolBasedTransfer.js --network optim | ||
``` | ||
|
||
- 检查跨链结果 | ||
``` | ||
npx hardhat run scripts/2-queryPoolBasedTrasnferStatus.js --network optim | ||
``` | ||
|
||
- 以映射方式进行跨链 | ||
``` | ||
npx hardhat run scripts/3.1-canonicalTokenTransfer.js --network optim | ||
``` | ||
|
||
- 检查跨链结果 | ||
大概需要 15 分钟左右才能确定最终跨链状态,可等待 15 分钟后再来查询结果 | ||
``` | ||
npx hardhat run scripts/4.1-queryCanonicalTrasnferStatus.js --network optim | ||
``` | ||
|
||
## 跨链 Refund | ||
当跨链失败的时候,用户可以 refund 他的资产,以下测试如何进行 refund | ||
|
||
- 以映射方式进行跨链 | ||
``` | ||
## 为测试需要,本次跨链将会失败,OP tonken 会被 unlock,为后续的 refund 做准备 | ||
npx hardhat run scripts/3.2-canonicalTokenTransfer_ForRefund.js --network optim | ||
``` | ||
|
||
- 检查跨链结果 | ||
大概需要 15 分钟左右才能确定最终跨链状态,可等待 15 分钟后再来查询结果 | ||
``` | ||
npx hardhat run scripts/4.2-queryCanonicalTrasnferStatus_ForRefund.js --network optim | ||
``` | ||
|
||
- Refund | ||
``` | ||
npx hardhat run scripts/5-canonicalTrasnferRefund.js --network optim | ||
``` | ||
|
||
## 参考文档 | ||
- 官方 doc: https://cbridge-docs.celer.network/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Celer Bridge | ||
[中文](./README-cn.md) / English | ||
|
||
## Overview | ||
cBridge introduces the best cross-chain token bridging experience, providing users with deep liquidity, efficient and easy-to-use liquidity management for cBridge node operators and liquidity providers who do not want to operate cBridge nodes. It also offers exciting developer-oriented features such as a universal message bridging for scenarios like cross-chain DEX and NFT. | ||
|
||
cBridge has two bridge modes, one is Pool-Based, and the other is Canonical Mapping. | ||
Pool-Based involves locking the same token on both Chain A and Chain B, taking USDT as an example. When a user needs to cross from Chain A to Chain B, they first deposit USDT into the Vault on Chain A, and then transfer the USDT to the user on Chain B. In this case, pools need to be established on Chain A and Chain B respectively to complete this operation. When the funds in the pool are insufficient, it may result in an inability to bridge. This mode is known as lock/unlock. | ||
Canonical Mapping corresponds to lock/mint. When a user bridges, they lock USDT into the vault on Chain A, and then mint the corresponding asset to the user on Chain B. Conversely, when a user wants to bridge USDT from Chain B back to Chain A, they burn the USDT on Chain B, and then transfer the USDT from the vault to the user on Chain A. | ||
|
||
This test will be conducted on the OP mainnet and Polygon mainnet. | ||
|
||
## Test Environment Requirements | ||
The node version needs to be v18.17.0, you can use nvm to switch to the current node version. | ||
|
||
|
||
## Preparing the Testing Environment | ||
- Install dependencies | ||
``` | ||
npm install | ||
``` | ||
|
||
- Configure the environment | ||
``` | ||
cp .env.example .env | ||
## Then configure the specific private key in .env | ||
``` | ||
|
||
## Executing Cross-Chain Operations | ||
- Cross-chain operation in Pool-Based mode | ||
``` | ||
npx hardhat run scripts/1-poolBasedTransfer.js --network optim | ||
``` | ||
|
||
- Check the cross-chain results | ||
``` | ||
npx hardhat run scripts/2-queryPoolBasedTrasnferStatus.js --network optim | ||
``` | ||
|
||
- Cross-chain operation in the mapping mode | ||
``` | ||
npx hardhat run scripts/3.1-canonicalTokenTransfer.js --network optim | ||
``` | ||
|
||
- Check the cross-chain results | ||
It takes approximately 15 minutes to confirm the final cross-chain status. You can wait for 15 minutes before querying the results. | ||
``` | ||
npx hardhat run scripts/4.1-queryCanonicalTrasnferStatus.js --network optim | ||
``` | ||
|
||
## Cross-Chain Refund | ||
When a cross-chain operation fails, users can refund their assets. The following tests demonstrate how to initiate a refund. | ||
|
||
- Cross-chain operation using the mapping method | ||
``` | ||
## For testing purposes, this cross-chain operation will fail, and the OP token will be unlocked, preparing for the subsequent refund. | ||
npx hardhat run scripts/3.2-canonicalTokenTransfer_ForRefund.js --network optim | ||
``` | ||
|
||
- Check the cross-chain results | ||
It takes approximately 15 minutes to confirm the final cross-chain status. You can wait for 15 minutes before querying the results. | ||
``` | ||
npx hardhat run scripts/4.2-queryCanonicalTrasnferStatus_ForRefund.js --network optim | ||
``` | ||
|
||
- Refund | ||
``` | ||
npx hardhat run scripts/5-canonicalTrasnferRefund.js --network optim | ||
``` | ||
|
||
## Reference Documentation | ||
- Official doc: https://cbridge-docs.celer.network/ |
189 changes: 189 additions & 0 deletions
189
basic/80-crossChainTransfer/celerBridge/contracts/Bridge.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
|
||
pragma solidity 0.8.17; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import "./libraries/PbBridge.sol"; | ||
import "./Pool.sol"; | ||
|
||
/** | ||
* @title The liquidity-pool based bridge. | ||
*/ | ||
contract Bridge is Pool { | ||
using SafeERC20 for IERC20; | ||
|
||
// liquidity events | ||
event Send( | ||
bytes32 transferId, | ||
address sender, | ||
address receiver, | ||
address token, | ||
uint256 amount, | ||
uint64 dstChainId, | ||
uint64 nonce, | ||
uint32 maxSlippage | ||
); | ||
event Relay( | ||
bytes32 transferId, | ||
address sender, | ||
address receiver, | ||
address token, | ||
uint256 amount, | ||
uint64 srcChainId, | ||
bytes32 srcTransferId | ||
); | ||
// gov events | ||
event MinSendUpdated(address token, uint256 amount); | ||
event MaxSendUpdated(address token, uint256 amount); | ||
|
||
mapping(bytes32 => bool) public transfers; | ||
mapping(address => uint256) public minSend; // send _amount must > minSend | ||
mapping(address => uint256) public maxSend; | ||
|
||
// min allowed max slippage uint32 value is slippage * 1M, eg. 0.5% -> 5000 | ||
uint32 public minimalMaxSlippage; | ||
|
||
/** | ||
* @notice Send a cross-chain transfer via the liquidity pool-based bridge. | ||
* NOTE: This function DOES NOT SUPPORT fee-on-transfer / rebasing tokens. | ||
* @param _receiver The address of the receiver. | ||
* @param _token The address of the token. | ||
* @param _amount The amount of the transfer. | ||
* @param _dstChainId The destination chain ID. | ||
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice. | ||
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%. | ||
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the | ||
* transfer can be refunded. | ||
*/ | ||
function send( | ||
address _receiver, | ||
address _token, | ||
uint256 _amount, | ||
uint64 _dstChainId, | ||
uint64 _nonce, | ||
uint32 _maxSlippage // slippage * 1M, eg. 0.5% -> 5000 | ||
) external nonReentrant whenNotPaused { | ||
bytes32 transferId = _send(_receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage); | ||
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); | ||
emit Send(transferId, msg.sender, _receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage); | ||
} | ||
|
||
/** | ||
* @notice Send a cross-chain transfer via the liquidity pool-based bridge using the native token. | ||
* @param _receiver The address of the receiver. | ||
* @param _amount The amount of the transfer. | ||
* @param _dstChainId The destination chain ID. | ||
* @param _nonce A unique number. Can be timestamp in practice. | ||
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%. | ||
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the | ||
* transfer can be refunded. | ||
*/ | ||
function sendNative( | ||
address _receiver, | ||
uint256 _amount, | ||
uint64 _dstChainId, | ||
uint64 _nonce, | ||
uint32 _maxSlippage | ||
) external payable nonReentrant whenNotPaused { | ||
require(msg.value == _amount, "Amount mismatch"); | ||
require(nativeWrap != address(0), "Native wrap not set"); | ||
bytes32 transferId = _send(_receiver, nativeWrap, _amount, _dstChainId, _nonce, _maxSlippage); | ||
IWETH(nativeWrap).deposit{value: _amount}(); | ||
emit Send(transferId, msg.sender, _receiver, nativeWrap, _amount, _dstChainId, _nonce, _maxSlippage); | ||
} | ||
|
||
function _send( | ||
address _receiver, | ||
address _token, | ||
uint256 _amount, | ||
uint64 _dstChainId, | ||
uint64 _nonce, | ||
uint32 _maxSlippage | ||
) private returns (bytes32) { | ||
require(_amount > minSend[_token], "amount too small"); | ||
require(maxSend[_token] == 0 || _amount <= maxSend[_token], "amount too large"); | ||
require(_maxSlippage > minimalMaxSlippage, "max slippage too small"); | ||
bytes32 transferId = keccak256( | ||
// uint64(block.chainid) for consistency as entire system uses uint64 for chain id | ||
// len = 20 + 20 + 20 + 32 + 8 + 8 + 8 = 116 | ||
abi.encodePacked(msg.sender, _receiver, _token, _amount, _dstChainId, _nonce, uint64(block.chainid)) | ||
); | ||
require(transfers[transferId] == false, "transfer exists"); | ||
transfers[transferId] = true; | ||
return transferId; | ||
} | ||
|
||
/** | ||
* @notice Relay a cross-chain transfer sent from a liquidity pool-based bridge on another chain. | ||
* @param _relayRequest The serialized Relay protobuf. | ||
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by | ||
* +2/3 of the bridge's current signing power to be delivered. | ||
* @param _signers The sorted list of signers. | ||
* @param _powers The signing powers of the signers. | ||
*/ | ||
function relay( | ||
bytes calldata _relayRequest, | ||
bytes[] calldata _sigs, | ||
address[] calldata _signers, | ||
uint256[] calldata _powers | ||
) external whenNotPaused { | ||
bytes32 domain = keccak256(abi.encodePacked(block.chainid, address(this), "Relay")); | ||
verifySigs(abi.encodePacked(domain, _relayRequest), _sigs, _signers, _powers); | ||
PbBridge.Relay memory request = PbBridge.decRelay(_relayRequest); | ||
// len = 20 + 20 + 20 + 32 + 8 + 8 + 32 = 140 | ||
bytes32 transferId = keccak256( | ||
abi.encodePacked( | ||
request.sender, | ||
request.receiver, | ||
request.token, | ||
request.amount, | ||
request.srcChainId, | ||
request.dstChainId, | ||
request.srcTransferId | ||
) | ||
); | ||
require(transfers[transferId] == false, "transfer exists"); | ||
transfers[transferId] = true; | ||
_updateVolume(request.token, request.amount); | ||
uint256 delayThreshold = delayThresholds[request.token]; | ||
if (delayThreshold > 0 && request.amount > delayThreshold) { | ||
_addDelayedTransfer(transferId, request.receiver, request.token, request.amount); | ||
} else { | ||
_sendToken(request.receiver, request.token, request.amount); | ||
} | ||
|
||
emit Relay( | ||
transferId, | ||
request.sender, | ||
request.receiver, | ||
request.token, | ||
request.amount, | ||
request.srcChainId, | ||
request.srcTransferId | ||
); | ||
} | ||
|
||
function setMinSend(address[] calldata _tokens, uint256[] calldata _amounts) external onlyGovernor { | ||
require(_tokens.length == _amounts.length, "length mismatch"); | ||
for (uint256 i = 0; i < _tokens.length; i++) { | ||
minSend[_tokens[i]] = _amounts[i]; | ||
emit MinSendUpdated(_tokens[i], _amounts[i]); | ||
} | ||
} | ||
|
||
function setMaxSend(address[] calldata _tokens, uint256[] calldata _amounts) external onlyGovernor { | ||
require(_tokens.length == _amounts.length, "length mismatch"); | ||
for (uint256 i = 0; i < _tokens.length; i++) { | ||
maxSend[_tokens[i]] = _amounts[i]; | ||
emit MaxSendUpdated(_tokens[i], _amounts[i]); | ||
} | ||
} | ||
|
||
function setMinimalMaxSlippage(uint32 _minimalMaxSlippage) external onlyGovernor { | ||
minimalMaxSlippage = _minimalMaxSlippage; | ||
} | ||
|
||
// This is needed to receive ETH when calling `IWETH.withdraw` | ||
receive() external payable {} | ||
} |
Oops, something went wrong.