Repository navigation
Expand file tree
/
Copy pathBridge.sol
More file actions
324 lines (268 loc) · 10.9 KB
/
Copy pathBridge.sol
File metadata and controls
324 lines (268 loc) · 10.9 KB
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
// SPDX-License-Identifier: Apache 2.0
pragma solidity =0.8.28;
import {Burner} from "../library/utils/Burner.sol";
import {RelayerGuard} from "../relayer/RelayerGuard.sol";
import {RateLimiter} from "../library/utils/RateLimiter.sol";
import {PreDeployedAddresses} from "../library/constants/Predeployed.sol";
import {IBridge} from "../interfaces/bridge/Bridge.sol";
import {IBridgeParam} from "../interfaces/bridge/BridgeParam.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
contract Bridge is Ownable, RelayerGuard, RateLimiter, IBridge, IBridgeParam, IERC165 {
using Address for address payable;
DepositParam public depositParam;
WithdrawParam public withdrawParam;
mapping(bytes32 txh => bool yes) internal deposits;
Withdrawal[] public withdrawals;
// 1 satoshi = 10 gwei
uint256 public constant SATOSHI = 10 gwei;
// the current dust value is 546, we use a slightly larger value here
uint256 public constant DUST = 1e3 * SATOSHI;
// 4 p2wsh/p2pkh input + 1 p2tr/p2wsh output + 1 change output
uint256 public constant BASE_TX_SIZE = 300 * SATOSHI;
uint256 public constant WITHDRAWAL_UPDATED_DURATION = 5 minutes;
// the max tax base points
uint256 internal constant MAX_BASE_POINT = 1e4;
// It is only for testing
constructor(address owner, bytes4 prefix) Ownable(owner) RateLimiter(32, false) {
depositParam = DepositParam({
prefix: prefix,
min: 1_000_000 gwei, // 0.001 btc
taxRate: 0,
maxTax: 0,
confirmations: 6
});
withdrawParam = WithdrawParam({
maxTax: 2_000_000 gwei, // 0.002 btc
taxRate: 20,
min: 1_000_000 gwei // 0.001 btc
});
}
/**
* deposit adds balance to the target address
* goat performs the adding outside EVM to prevent any errors
* @param txHash the txHash(LE)
* @param txout the txout
* @param target the depoist address
* @param amount the deposit amount without the tax
* @param tax the deposit tax
*/
function deposit(bytes32 txHash, uint32 txout, address target, uint256 amount, uint256 tax)
external
override
OnlyRelayer
{
bytes32 depositHash = keccak256(abi.encodePacked(txHash, txout));
require(!deposits[depositHash], "duplicated");
deposits[depositHash] = true;
emit Deposit(target, txHash, txout, amount, tax);
// Add balance to the target and pay the tax to GF in the runtime
}
/**
* isDeposited checks if the deposit is succeed
* @param txHash the txHash(LE)
* @param txout the txout index
*/
function isDeposited(bytes32 txHash, uint32 txout) external view override returns (bool) {
bytes32 depositHash = keccak256(abi.encodePacked(txHash, txout));
return deposits[depositHash];
}
/**
* withdraw initializes a new withdrawal request by a user
* @param receiver the address to withdraw
* @param maxTxPrice the max allowed tx price in sat/vbyte
*
* consensus layer has a complete validation for the receiver address
* if it's invalid, the consensus layer will send back `cancel2` tx to reject it
*/
function withdraw(string calldata receiver, uint16 maxTxPrice) external payable override RateLimiting {
uint256 addrLength = bytes(receiver).length;
require(addrLength > 33 && addrLength < 91, "invalid address");
uint256 amount = msg.value;
uint256 tax = 0;
WithdrawParam memory p = withdrawParam;
require(amount >= p.min, "amount too low");
if (p.taxRate > 0) {
tax = (amount * p.taxRate) / MAX_BASE_POINT;
if (p.maxTax > 0 && tax > p.maxTax) {
tax = p.maxTax;
}
amount -= tax;
}
// dust as tax
uint256 dust = amount % SATOSHI;
if (dust > 0) {
tax += dust;
amount -= dust;
}
require(maxTxPrice > 0, "invalid tx price");
require(amount > DUST + maxTxPrice * BASE_TX_SIZE, "unaffordable");
uint256 id = withdrawals.length;
withdrawals.push(
Withdrawal({
sender: msg.sender,
amount: amount,
tax: tax,
maxTxPrice: maxTxPrice,
updatedAt: block.timestamp,
status: WithdrawalStatus.Pending
})
);
emit Withdraw(id, msg.sender, amount, tax, maxTxPrice, receiver);
}
/**
* replaceByFee updates the withdrawal tx price
* @param wid the withdrawal id
* @param maxTxPrice the new max tx price
*/
function replaceByFee(uint256 wid, uint16 maxTxPrice) external override RateLimiting {
Withdrawal storage withdrawal = withdrawals[wid];
if (withdrawal.status != WithdrawalStatus.Pending) {
revert Forbidden();
}
if (withdrawal.sender != msg.sender) {
revert AccessDenied();
}
if (block.timestamp - withdrawal.updatedAt < WITHDRAWAL_UPDATED_DURATION) {
revert RequestTooFrequent();
}
require(maxTxPrice > withdrawal.maxTxPrice, "invalid tx price");
require(withdrawal.amount > DUST + maxTxPrice * BASE_TX_SIZE, "unaffordable");
withdrawal.maxTxPrice = maxTxPrice;
withdrawal.updatedAt = block.timestamp;
emit RBF(wid, maxTxPrice);
}
/**
* cancel1 cancels the withdrawal by origin user
* @param wid the withdrawal id
*/
function cancel1(uint256 wid) external RateLimiting {
Withdrawal storage withdrawal = withdrawals[wid];
if (withdrawal.status != WithdrawalStatus.Pending) {
revert Forbidden();
}
if (withdrawal.sender != msg.sender) {
revert AccessDenied();
}
if (block.timestamp - withdrawal.updatedAt < WITHDRAWAL_UPDATED_DURATION) {
revert RequestTooFrequent();
}
withdrawal.updatedAt = block.timestamp;
withdrawal.status = WithdrawalStatus.Canceling;
emit Canceling(wid);
}
/**
* cancel2 apporves the cancellation request by relayer
* relayer can pay the withdrawal to disregard the cancellation request
* relayer can reject a pending withdrawal as well
* @param wid the withdrwal id
*/
function cancel2(uint256 wid) external OnlyRelayer {
Withdrawal storage withdrawal = withdrawals[wid];
WithdrawalStatus status = withdrawal.status;
require(status == WithdrawalStatus.Pending || status == WithdrawalStatus.Canceling);
withdrawal.status = WithdrawalStatus.Canceled;
withdrawal.updatedAt = block.timestamp;
emit Canceled(wid);
}
/**
* refund refunds the amount of the canceled withdrawal to the origin user
* @param wid the withdrwal id
*/
function refund(uint256 wid) external {
Withdrawal storage withdrawal = withdrawals[wid];
if (withdrawal.status != WithdrawalStatus.Canceled) {
revert Forbidden();
}
withdrawal.status = WithdrawalStatus.Refunded;
address payable owner = payable(withdrawal.sender);
if (owner != msg.sender) {
revert AccessDenied();
}
withdrawal.updatedAt = block.timestamp;
// refund to the owner
owner.sendValue(withdrawal.amount + withdrawal.tax);
emit Refund(wid);
}
/**
* paid finalizes the withdrawal request and burns the withdrawal amount from network
* It aslo transfers the tax to GF address if the tax is enabled
* @param wid withdrawal id
* @param txHash the withdrawal txHash(LE)
* @param txout the tx output index
* @param received the actual paid amount
*/
function paid(uint256 wid, bytes32 txHash, uint32 txout, uint256 received) external OnlyRelayer {
Withdrawal storage withdrawal = withdrawals[wid];
WithdrawalStatus status = withdrawal.status;
require(status == WithdrawalStatus.Pending || status == WithdrawalStatus.Canceling);
withdrawal.status = WithdrawalStatus.Paid;
withdrawal.updatedAt = block.timestamp;
// send the tax to GF
uint256 tax = withdrawal.tax;
if (tax > 0) {
PreDeployedAddresses.GoatFoundation.sendValue(tax);
}
// Burn the withdrawal amount from network
new Burner{value: withdrawal.amount, salt: bytes32(0x0)}();
emit Paid(wid, txHash, txout, received);
}
modifier checkTax(uint16 bp, uint64 max) {
require(max % SATOSHI == 0 && bp < 101, InvalidTax());
_;
}
/**
* setWithdrawalTax updates current withdrawal tax config
* @param bp the basic point for the withdrawal tax rate
* @param max the max tax in wei
*/
function setWithdrawalTax(uint16 bp, uint64 max) external override onlyOwner checkTax(bp, max) {
withdrawParam.taxRate = bp;
withdrawParam.maxTax = max;
emit WithdrawalTaxUpdated(bp, max);
}
/**
* setDepositTax updates current deposit tax config
* @param bp the basic point for the deposit tax rate
* @param max the max tax in wei
*/
function setDepositTax(uint16 bp, uint64 max) external override onlyOwner checkTax(bp, max) {
depositParam.taxRate = bp;
depositParam.maxTax = max;
emit DepositTaxUpdated(bp, max);
}
modifier checkThreshold(uint64 amount) {
require(amount < 1e18 && amount >= 1e14 && amount % SATOSHI == 0, InvalidThreshold());
_;
}
/**
* setMinWithdrawal updates current min withdrawal amount
* @param amount the amount in wei, the amount should be in range [0.0001 btc, 1btc)
*/
function setMinWithdrawal(uint64 amount) external override onlyOwner checkThreshold(amount) {
withdrawParam.min = amount;
emit MinWithdrawalUpdated(amount);
}
/**
* setMinDeposit updates the min deposit
* @param amount the amount in wei, the amount should be in range [0.0001 btc, 1btc)
*/
function setMinDeposit(uint64 amount) external override onlyOwner checkThreshold(amount) {
depositParam.min = amount;
emit MinDepositUpdated(amount);
}
/**
* setConfirmationNumber updates current confirmation number
* @param number the confirmation number, using 6 for mainnet and 20 for testnet
*/
function setConfirmationNumber(uint16 number) external override onlyOwner {
require(number > 0, "number too low");
depositParam.confirmations = number;
emit ConfirmationNumberUpdated(number);
}
function supportsInterface(bytes4 id) external view virtual override returns (bool) {
return
id == type(IERC165).interfaceId || id == type(IBridge).interfaceId || id == type(IBridgeParam).interfaceId;
}
}