Skip to content

Commit 74bdbca

Browse files
authored
Merge pull request #1026 from yingjingyang/main
add chainlink CCIP && Celer bridge
2 parents 8b5c32c + 5c032f5 commit 74bdbca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+7193
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
PRIVATE_KEY=xxxx
2+
PRIVATE_KEY1=yyyy
3+
PRIVATE_KEY2=yyyy
4+
INFURA_ID=yyyy
5+
PROJECT_ID=yyyy
6+
TARGET_ACCOUNT=yyyy
7+
API_KEY=yyyy
8+
EHTERSCAN_KEY=yyyy
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules
2+
.env
3+
coverage
4+
coverage.json
5+
typechain
6+
7+
#Hardhat files
8+
cache
9+
artifacts
10+
11+
**/deployment.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Celer Bridge 跨链
2+
中文 / [English](./README.md)
3+
4+
## 概览
5+
cBridge 引入了最佳的跨链代币桥接体验,为用户提供了深度流动性,为 cBridge 节点运营商和不想运营 cBridge 节点的流动性提供者提供了高效且易于使用的流动性管理,以及新的令人兴奋的面向开发者的功能,如用于跨链 DEX 和 NFT 等场景的通用消息桥接。
6+
7+
cBridge 有两种 bridge 方式,一种是 Pool-Based ,一种是 Canonical Mapping。
8+
Pool-Based 就是在 A 链和 B 链之间各自锁定相同的 token,比如 USDT。当用户需要从 A 链跨到 B 链的时候,先会把 USDT 转入 A 链这边的 Vault 中,然后在 B 链这边把 USDT transfer 给用户。这个情况下,就需要在 A 链和 B 链上各自建立 pool 来完成这个操作,当 pool 中的资金不足时,就会出现无法 bridge 的情况。这种模式被称为 lock/unlock。
9+
Canonical Mapping 对应的就是 lock/mint。用户 bridge 的时候,会把 USDT lock 到 A 链这边的 vault,然后在 B 链这边 mint 出对应的资产给用户。反过来,当用户想把 B 链上的 USDT bridge 会 A 链的时候,会把 B 链上的 USDT burn 掉,然后在 A 链这边把 USDT 从 vault 再 transfer 给用户。
10+
11+
本测试将以 OP mainnet 和 Polygon mainnet 进行测试
12+
13+
## 测试环境要求
14+
node 版本需要为 v18.17.0, 可以使用 nvm 切换当前 node 版本
15+
16+
## 准备测试环境
17+
- 安装依赖
18+
```
19+
npm install
20+
```
21+
22+
- 配置环境
23+
```
24+
cp .env.example .env
25+
## 之后在 .env 中配置具体的私钥
26+
```
27+
28+
## 执行跨链
29+
- 以流动性池方式进行跨链
30+
```
31+
npx hardhat run scripts/1-poolBasedTransfer.js --network optim
32+
```
33+
34+
- 检查跨链结果
35+
```
36+
npx hardhat run scripts/2-queryPoolBasedTrasnferStatus.js --network optim
37+
```
38+
39+
- 以映射方式进行跨链
40+
```
41+
npx hardhat run scripts/3.1-canonicalTokenTransfer.js --network optim
42+
```
43+
44+
- 检查跨链结果
45+
大概需要 15 分钟左右才能确定最终跨链状态,可等待 15 分钟后再来查询结果
46+
```
47+
npx hardhat run scripts/4.1-queryCanonicalTrasnferStatus.js --network optim
48+
```
49+
50+
## 跨链 Refund
51+
当跨链失败的时候,用户可以 refund 他的资产,以下测试如何进行 refund
52+
53+
- 以映射方式进行跨链
54+
```
55+
## 为测试需要,本次跨链将会失败,OP tonken 会被 unlock,为后续的 refund 做准备
56+
npx hardhat run scripts/3.2-canonicalTokenTransfer_ForRefund.js --network optim
57+
```
58+
59+
- 检查跨链结果
60+
大概需要 15 分钟左右才能确定最终跨链状态,可等待 15 分钟后再来查询结果
61+
```
62+
npx hardhat run scripts/4.2-queryCanonicalTrasnferStatus_ForRefund.js --network optim
63+
```
64+
65+
- Refund
66+
```
67+
npx hardhat run scripts/5-canonicalTrasnferRefund.js --network optim
68+
```
69+
70+
## 参考文档
71+
- 官方 doc: https://cbridge-docs.celer.network/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Celer Bridge
2+
[中文](./README-cn.md) / English
3+
4+
## Overview
5+
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.
6+
7+
cBridge has two bridge modes, one is Pool-Based, and the other is Canonical Mapping.
8+
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.
9+
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.
10+
11+
This test will be conducted on the OP mainnet and Polygon mainnet.
12+
13+
## Test Environment Requirements
14+
The node version needs to be v18.17.0, you can use nvm to switch to the current node version.
15+
16+
17+
## Preparing the Testing Environment
18+
- Install dependencies
19+
```
20+
npm install
21+
```
22+
23+
- Configure the environment
24+
```
25+
cp .env.example .env
26+
## Then configure the specific private key in .env
27+
```
28+
29+
## Executing Cross-Chain Operations
30+
- Cross-chain operation in Pool-Based mode
31+
```
32+
npx hardhat run scripts/1-poolBasedTransfer.js --network optim
33+
```
34+
35+
- Check the cross-chain results
36+
```
37+
npx hardhat run scripts/2-queryPoolBasedTrasnferStatus.js --network optim
38+
```
39+
40+
- Cross-chain operation in the mapping mode
41+
```
42+
npx hardhat run scripts/3.1-canonicalTokenTransfer.js --network optim
43+
```
44+
45+
- Check the cross-chain results
46+
It takes approximately 15 minutes to confirm the final cross-chain status. You can wait for 15 minutes before querying the results.
47+
```
48+
npx hardhat run scripts/4.1-queryCanonicalTrasnferStatus.js --network optim
49+
```
50+
51+
## Cross-Chain Refund
52+
When a cross-chain operation fails, users can refund their assets. The following tests demonstrate how to initiate a refund.
53+
54+
- Cross-chain operation using the mapping method
55+
```
56+
## For testing purposes, this cross-chain operation will fail, and the OP token will be unlocked, preparing for the subsequent refund.
57+
npx hardhat run scripts/3.2-canonicalTokenTransfer_ForRefund.js --network optim
58+
```
59+
60+
- Check the cross-chain results
61+
It takes approximately 15 minutes to confirm the final cross-chain status. You can wait for 15 minutes before querying the results.
62+
```
63+
npx hardhat run scripts/4.2-queryCanonicalTrasnferStatus_ForRefund.js --network optim
64+
```
65+
66+
- Refund
67+
```
68+
npx hardhat run scripts/5-canonicalTrasnferRefund.js --network optim
69+
```
70+
71+
## Reference Documentation
72+
- Official doc: https://cbridge-docs.celer.network/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
3+
pragma solidity 0.8.17;
4+
5+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7+
import "./libraries/PbBridge.sol";
8+
import "./Pool.sol";
9+
10+
/**
11+
* @title The liquidity-pool based bridge.
12+
*/
13+
contract Bridge is Pool {
14+
using SafeERC20 for IERC20;
15+
16+
// liquidity events
17+
event Send(
18+
bytes32 transferId,
19+
address sender,
20+
address receiver,
21+
address token,
22+
uint256 amount,
23+
uint64 dstChainId,
24+
uint64 nonce,
25+
uint32 maxSlippage
26+
);
27+
event Relay(
28+
bytes32 transferId,
29+
address sender,
30+
address receiver,
31+
address token,
32+
uint256 amount,
33+
uint64 srcChainId,
34+
bytes32 srcTransferId
35+
);
36+
// gov events
37+
event MinSendUpdated(address token, uint256 amount);
38+
event MaxSendUpdated(address token, uint256 amount);
39+
40+
mapping(bytes32 => bool) public transfers;
41+
mapping(address => uint256) public minSend; // send _amount must > minSend
42+
mapping(address => uint256) public maxSend;
43+
44+
// min allowed max slippage uint32 value is slippage * 1M, eg. 0.5% -> 5000
45+
uint32 public minimalMaxSlippage;
46+
47+
/**
48+
* @notice Send a cross-chain transfer via the liquidity pool-based bridge.
49+
* NOTE: This function DOES NOT SUPPORT fee-on-transfer / rebasing tokens.
50+
* @param _receiver The address of the receiver.
51+
* @param _token The address of the token.
52+
* @param _amount The amount of the transfer.
53+
* @param _dstChainId The destination chain ID.
54+
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
55+
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%.
56+
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
57+
* transfer can be refunded.
58+
*/
59+
function send(
60+
address _receiver,
61+
address _token,
62+
uint256 _amount,
63+
uint64 _dstChainId,
64+
uint64 _nonce,
65+
uint32 _maxSlippage // slippage * 1M, eg. 0.5% -> 5000
66+
) external nonReentrant whenNotPaused {
67+
bytes32 transferId = _send(_receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage);
68+
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
69+
emit Send(transferId, msg.sender, _receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage);
70+
}
71+
72+
/**
73+
* @notice Send a cross-chain transfer via the liquidity pool-based bridge using the native token.
74+
* @param _receiver The address of the receiver.
75+
* @param _amount The amount of the transfer.
76+
* @param _dstChainId The destination chain ID.
77+
* @param _nonce A unique number. Can be timestamp in practice.
78+
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%.
79+
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
80+
* transfer can be refunded.
81+
*/
82+
function sendNative(
83+
address _receiver,
84+
uint256 _amount,
85+
uint64 _dstChainId,
86+
uint64 _nonce,
87+
uint32 _maxSlippage
88+
) external payable nonReentrant whenNotPaused {
89+
require(msg.value == _amount, "Amount mismatch");
90+
require(nativeWrap != address(0), "Native wrap not set");
91+
bytes32 transferId = _send(_receiver, nativeWrap, _amount, _dstChainId, _nonce, _maxSlippage);
92+
IWETH(nativeWrap).deposit{value: _amount}();
93+
emit Send(transferId, msg.sender, _receiver, nativeWrap, _amount, _dstChainId, _nonce, _maxSlippage);
94+
}
95+
96+
function _send(
97+
address _receiver,
98+
address _token,
99+
uint256 _amount,
100+
uint64 _dstChainId,
101+
uint64 _nonce,
102+
uint32 _maxSlippage
103+
) private returns (bytes32) {
104+
require(_amount > minSend[_token], "amount too small");
105+
require(maxSend[_token] == 0 || _amount <= maxSend[_token], "amount too large");
106+
require(_maxSlippage > minimalMaxSlippage, "max slippage too small");
107+
bytes32 transferId = keccak256(
108+
// uint64(block.chainid) for consistency as entire system uses uint64 for chain id
109+
// len = 20 + 20 + 20 + 32 + 8 + 8 + 8 = 116
110+
abi.encodePacked(msg.sender, _receiver, _token, _amount, _dstChainId, _nonce, uint64(block.chainid))
111+
);
112+
require(transfers[transferId] == false, "transfer exists");
113+
transfers[transferId] = true;
114+
return transferId;
115+
}
116+
117+
/**
118+
* @notice Relay a cross-chain transfer sent from a liquidity pool-based bridge on another chain.
119+
* @param _relayRequest The serialized Relay protobuf.
120+
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
121+
* +2/3 of the bridge's current signing power to be delivered.
122+
* @param _signers The sorted list of signers.
123+
* @param _powers The signing powers of the signers.
124+
*/
125+
function relay(
126+
bytes calldata _relayRequest,
127+
bytes[] calldata _sigs,
128+
address[] calldata _signers,
129+
uint256[] calldata _powers
130+
) external whenNotPaused {
131+
bytes32 domain = keccak256(abi.encodePacked(block.chainid, address(this), "Relay"));
132+
verifySigs(abi.encodePacked(domain, _relayRequest), _sigs, _signers, _powers);
133+
PbBridge.Relay memory request = PbBridge.decRelay(_relayRequest);
134+
// len = 20 + 20 + 20 + 32 + 8 + 8 + 32 = 140
135+
bytes32 transferId = keccak256(
136+
abi.encodePacked(
137+
request.sender,
138+
request.receiver,
139+
request.token,
140+
request.amount,
141+
request.srcChainId,
142+
request.dstChainId,
143+
request.srcTransferId
144+
)
145+
);
146+
require(transfers[transferId] == false, "transfer exists");
147+
transfers[transferId] = true;
148+
_updateVolume(request.token, request.amount);
149+
uint256 delayThreshold = delayThresholds[request.token];
150+
if (delayThreshold > 0 && request.amount > delayThreshold) {
151+
_addDelayedTransfer(transferId, request.receiver, request.token, request.amount);
152+
} else {
153+
_sendToken(request.receiver, request.token, request.amount);
154+
}
155+
156+
emit Relay(
157+
transferId,
158+
request.sender,
159+
request.receiver,
160+
request.token,
161+
request.amount,
162+
request.srcChainId,
163+
request.srcTransferId
164+
);
165+
}
166+
167+
function setMinSend(address[] calldata _tokens, uint256[] calldata _amounts) external onlyGovernor {
168+
require(_tokens.length == _amounts.length, "length mismatch");
169+
for (uint256 i = 0; i < _tokens.length; i++) {
170+
minSend[_tokens[i]] = _amounts[i];
171+
emit MinSendUpdated(_tokens[i], _amounts[i]);
172+
}
173+
}
174+
175+
function setMaxSend(address[] calldata _tokens, uint256[] calldata _amounts) external onlyGovernor {
176+
require(_tokens.length == _amounts.length, "length mismatch");
177+
for (uint256 i = 0; i < _tokens.length; i++) {
178+
maxSend[_tokens[i]] = _amounts[i];
179+
emit MaxSendUpdated(_tokens[i], _amounts[i]);
180+
}
181+
}
182+
183+
function setMinimalMaxSlippage(uint32 _minimalMaxSlippage) external onlyGovernor {
184+
minimalMaxSlippage = _minimalMaxSlippage;
185+
}
186+
187+
// This is needed to receive ETH when calling `IWETH.withdraw`
188+
receive() external payable {}
189+
}

0 commit comments

Comments
 (0)