-
Notifications
You must be signed in to change notification settings - Fork 17
/
TokenLocker.sol
333 lines (297 loc) · 12.8 KB
/
TokenLocker.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma abicoder v2;
import {CKBCrypto} from "./libraries/CKBCrypto.sol";
import {TypedMemView} from "./libraries/TypedMemView.sol";
import {ViewSpv} from "./libraries/ViewSpv.sol";
import {Address} from "./libraries/Address.sol";
import {SafeERC20} from "./libraries/SafeERC20.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import {ICKBSpv} from "./interfaces/ICKBSpv.sol";
import {Blake2b} from "./libraries/Blake2b.sol";
//import "hardhat/console.sol";
contract TokenLocker {
using Address for address;
using TypedMemView for bytes;
using TypedMemView for bytes29;
using ViewSpv for bytes29;
using SafeERC20 for IERC20;
bool public initialized;
uint8 public recipientCellTypescriptHashType_;
uint64 public numConfirmations_;
ICKBSpv public ckbSpv_;
bytes32 public recipientCellTypescriptCodeHash_;
bytes32 public lightClientTypescriptHash_;
bytes32 public bridgeCellLockscriptCodeHash_;
// txHash -> Used
mapping(bytes32 => bool) public usedTx_;
struct TreeNode {
uint64 index;
bytes32 data;
}
event Locked(
address indexed token,
address indexed sender,
uint256 lockedAmount,
uint256 bridgeFee,
bytes recipientLockscript,
bytes replayResistOutpoint,
bytes sudtExtraData
);
event Unlocked(
address indexed token,
address indexed recipient,
address indexed sender,
uint256 receivedAmount,
uint256 bridgeFee
);
function initialize(
address ckbSpvAddress,
uint64 numConfirmations,
bytes32 recipientCellTypescriptCodeHash,
uint8 typescriptHashType,
bytes32 lightClientTypescriptHash,
bytes32 bridgeCellLockscriptCodeHash
) public {
require(!initialized, "Contract instance has already been initialized");
initialized = true;
ckbSpv_ = ICKBSpv(ckbSpvAddress);
numConfirmations_ = numConfirmations;
recipientCellTypescriptCodeHash_ = recipientCellTypescriptCodeHash;
recipientCellTypescriptHashType_ = typescriptHashType;
lightClientTypescriptHash_ = lightClientTypescriptHash;
bridgeCellLockscriptCodeHash_ = bridgeCellLockscriptCodeHash;
}
function lockETH(
uint256 bridgeFee,
bytes memory recipientLockscript,
bytes memory replayResistOutpoint,
bytes memory sudtExtraData
) public payable {
require(msg.value > bridgeFee, "fee should not exceed bridge amount");
emit Locked(
address(0),
msg.sender,
msg.value,
bridgeFee,
recipientLockscript,
replayResistOutpoint,
sudtExtraData
);
}
// before lockToken, user should approve -> TokenLocker Contract with 0xffffff token
function lockToken(
address token,
uint256 amount,
uint256 bridgeFee,
bytes memory recipientLockscript,
bytes memory replayResistOutpoint,
bytes memory sudtExtraData
) public {
require(amount > bridgeFee, "fee should not exceed bridge amount");
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
emit Locked(
token,
msg.sender,
amount,
bridgeFee,
recipientLockscript,
replayResistOutpoint,
sudtExtraData
);
}
function unlockToken(bytes memory proof) external {
// 1. getHistoryTxRootInfo from CkbChain
(uint64 initBlockNumber, uint64 latestBlockNumber, bytes32 targetHistoryTxRoot) = ckbSpv_.getHistoryTxRootInfo();
require(latestBlockNumber > 0, "ckbSpv should initialize");
// 2. check proveTxRootExist
bytes29 txProofVecView;
TreeNode[] memory leafNodes;
{
bytes29 proofView = proof.ref(uint40(ViewSpv.SpvTypes.CKBUnlockTokenParam));
bytes29 txRootProofView = proofView.historyTxRootProof();
txProofVecView = proofView.historyTxProofVec();
require(txRootProofView.initBlockNumber() == initBlockNumber, "initBlockNumber mismatch");
require(txRootProofView.latestBlockNumber() == latestBlockNumber, "latestBlockNumber mismatch");
leafNodes = _proveTxRootExist(txRootProofView, targetHistoryTxRoot);
}
// 3. check txs exist and unlockToken
bytes29 txProofView;
uint64 blockNumber;
uint length = txProofVecView.txProofLength();
uint64 merkleIndex;
for (uint i = 0; i < length; i++) {
txProofView = txProofVecView.getHistoryTxProofFromVec(i);
blockNumber = txProofView.txBlockNumber();
require(
blockNumber >= initBlockNumber,
"the blockNumber which the tx exists in should be greater than or equal to initBlockNumber"
);
require(
uint256(blockNumber) + uint256(numConfirmations_) <= uint256(latestBlockNumber),
"blockNumber from txProofData should not be greater than latestBlockNumber"
);
// - 1. check if txHashes from txProof and raw ckbTx match
bytes memory rawTx = txProofView.rawTransaction().clone();
bytes32 txHash = CKBCrypto.digest(rawTx, rawTx.length);
require(!usedTx_[txHash], "The burn tx cannot be reused");
usedTx_[txHash] = true;
// - 2. proveTxExist, check if txRoots from txProof and txRootProof match
// calc the index in txRoot-merkle-tree
// @dev refer to https://github.com/nervosnetwork/merkle-tree/blob/master/README.md
// in definition, merkleIndex = i+n-1
// n == all_leaves_count == latestBlockNumber - initBlockNumber + 1
// i == index of the item in all_leaves == blockNumber - initBlockNumber
// merkleIndex = ( blockNumber - initBlockNumber ) + (latestBlockNumber - initBlockNumber + 1) - 1
merkleIndex = (latestBlockNumber - initBlockNumber) + (blockNumber - initBlockNumber);
_proveTxExist(txProofView, txHash, _getTargetTxRoot(merkleIndex, leafNodes));
// - 3. unlockToken
(uint256 bridgeAmount, uint256 bridgeFee, address tokenAddress, address recipientAddress) = decodeBurnResult(rawTx);
require(bridgeAmount > bridgeFee, "fee should not exceed bridge amount");
uint256 receivedAmount = bridgeAmount - bridgeFee;
// address(0) means `ether` here
if (tokenAddress == address(0)) {
payable(recipientAddress).transfer(receivedAmount);
payable(msg.sender).transfer(bridgeFee);
} else {
IERC20(tokenAddress).safeTransfer(recipientAddress, receivedAmount);
IERC20(tokenAddress).safeTransfer(msg.sender, bridgeFee);
}
emit Unlocked(tokenAddress, recipientAddress, msg.sender, receivedAmount, bridgeFee);
}
}
function decodeBurnResult(bytes memory ckbTx) public view returns (
uint256 bridgeAmount,
uint256 bridgeFee,
address token,
address recipient
){
bytes29 rawTx = ckbTx.ref(uint40(ViewSpv.SpvTypes.RawTx));
bytes29 recipientCellTypescript = rawTx.outputs().recipientCellOutput().typescript();
require(
(recipientCellTypescript.recipientTypescriptCodeHash() == recipientCellTypescriptCodeHash_),
"invalid recipient cell typescript code hash"
);
require((recipientCellTypescript.hashType() == recipientCellTypescriptHashType_), "invalid recipient cell typescript hash type");
bytes29 recipientCellData = rawTx.outputsData().recipientCellData();
require((recipientCellData.contractAddress() == address(this)), "invalid contract address in recipient cell");
require((recipientCellData.lightClientTypescriptHash() == lightClientTypescriptHash_), "invalid lightClientTypescriptHash in recipient cell");
require((recipientCellData.bridgeLockscriptCodeHash() == bridgeCellLockscriptCodeHash_), "invalid bridgeLockscriptCodeHash in recipient cell");
return (
recipientCellData.bridgeAmount(),
recipientCellData.bridgeFee(),
recipientCellData.tokenAddress(),
recipientCellData.recipientAddress()
);
}
function _getTargetTxRoot(uint64 leafMerkleIndex, TreeNode[] memory leafNodes) internal pure returns (bytes32) {
for (uint i = 0; i < leafNodes.length; i++) {
if (leafMerkleIndex == leafNodes[i].index) {
return leafNodes[i].data;
}
}
return bytes32(0);
}
function _proveTxExist(bytes29 txProofView, bytes32 txHash, bytes32 targetTxRoot)
internal
view
returns (bool)
{
require(targetTxRoot != bytes32(0), "txRoot from the blockNumber is not in the proof");
uint16 index = txProofView.historyTxMerkleIndex();
uint16 sibling;
uint256 lemmasIndex = 0;
bytes29 lemmas = txProofView.historyLemmas();
uint256 length = lemmas.len() / 32;
// calc the rawTransactionsRoot
bytes32 rawTxRoot = txHash;
while (lemmasIndex < length && index > 0) {
sibling = ((index + 1) ^ 1) - 1;
if (index < sibling) {
rawTxRoot = Blake2b.digest64Merge(rawTxRoot, lemmas.indexH256Array(lemmasIndex));
} else {
rawTxRoot = Blake2b.digest64Merge(lemmas.indexH256Array(lemmasIndex), rawTxRoot);
}
lemmasIndex++;
// index = parent(index)
index = (index - 1) >> 1;
}
// calc the transactionsRoot by [rawTransactionsRoot, witnessesRoot]
bytes32 transactionsRoot = Blake2b.digest64Merge(rawTxRoot, txProofView.historyWitnessesRoot());
require(
transactionsRoot == targetTxRoot,
"tx proof not passed"
);
return true;
}
function _proveTxRootExist(bytes29 txRootProofView, bytes32 targetHistoryTxRoot)
internal
pure
returns (TreeNode[] memory leafNodes)
{
// queue
bytes29 indices = txRootProofView.indices();
uint leavesLength = indices.len() / 8;
uint queueLength = leavesLength + 1;
TreeNode[] memory queue = new TreeNode[](queueLength);
leafNodes = new TreeNode[](leavesLength);
uint front = 0;
uint end = 0;
// merkle tree indices and node(byte32) of leaves
{
bytes29 proofLeaves = txRootProofView.proofLeaves();
require(leavesLength > 0 && leavesLength == proofLeaves.len() / 32, "length of indices and proofLeaves mismatch");
TreeNode memory node;
for (uint i = 0; i < leavesLength; i++) {
node = TreeNode(indices.indexU64Array(i), proofLeaves.indexH256Array(i));
leafNodes[i] = node;
queue[end] = node;
end++;
}
}
// merkle tree lemmas
uint lemmasPosition = 0;
bytes29 lemmas = txRootProofView.txRootLemmas();
uint lemmasLength = lemmas.len() / 32;
// init
uint64 currentIndex;
bytes32 currentNode;
uint64 siblingIndex;
bytes32 siblingNode;
while (front != end) {
currentIndex = queue[front].index;
currentNode = queue[front].data;
front = (front + 1) % queueLength;
if (currentIndex == 0) {
// ensure that all lemmas and leaves are consumed
require(
lemmasPosition == lemmasLength && front == end,
"invalid historyTxRootProof, Not all of lemmas and leaves are consumed"
);
break;
}
siblingIndex = ((currentIndex + 1) ^ 1) - 1;
if (front != end && siblingIndex == queue[front].index) {
siblingNode = queue[front].data;
front = (front + 1) % queueLength;
} else {
require(lemmasPosition < lemmasLength, "invalid historyTxRootProof");
siblingNode = lemmas.indexH256Array(lemmasPosition);
lemmasPosition++;
}
// push parentTreeNode to queue
// parentIndex == (currentIndex - 1) >> 1, parentNode
if (currentIndex < siblingIndex) {
queue[end] = TreeNode((currentIndex - 1) >> 1, keccak256(abi.encodePacked(currentNode, siblingNode)));
} else {
queue[end] = TreeNode((currentIndex - 1) >> 1, keccak256(abi.encodePacked(siblingNode, currentNode)));
}
end = (end + 1) % queueLength;
}
require(
currentNode == targetHistoryTxRoot,
"proof not verified"
);
return leafNodes;
}
}