-
Notifications
You must be signed in to change notification settings - Fork 38
/
bridge.sol
568 lines (473 loc) · 21.9 KB
/
bridge.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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
pragma solidity ^0.4.19;
/// general helpers.
/// `internal` so they get compiled into contracts using them.
library Helpers {
/// returns whether `array` contains `value`.
function addressArrayContains(address[] array, address value) internal pure returns (bool) {
for (uint256 i = 0; i < array.length; i++) {
if (array[i] == value) {
return true;
}
}
return false;
}
// returns the digits of `inputValue` as a string.
// example: `uintToString(12345678)` returns `"12345678"`
function uintToString(uint256 inputValue) internal pure returns (string) {
// figure out the length of the resulting string
uint256 length = 0;
uint256 currentValue = inputValue;
do {
length++;
currentValue /= 10;
} while (currentValue != 0);
// allocate enough memory
bytes memory result = new bytes(length);
// construct the string backwards
uint256 i = length - 1;
currentValue = inputValue;
do {
result[i--] = byte(48 + currentValue % 10);
currentValue /= 10;
} while (currentValue != 0);
return string(result);
}
/// returns whether signatures (whose components are in `vs`, `rs`, `ss`)
/// contain `requiredSignatures` distinct correct signatures
/// where signer is in `allowed_signers`
/// that signed `message`
function hasEnoughValidSignatures(bytes message, uint8[] vs, bytes32[] rs, bytes32[] ss, address[] allowed_signers, uint256 requiredSignatures) internal pure returns (bool) {
// not enough signatures
if (vs.length < requiredSignatures) {
return false;
}
var hash = MessageSigning.hashMessage(message);
var encountered_addresses = new address[](allowed_signers.length);
for (uint256 i = 0; i < requiredSignatures; i++) {
var recovered_address = ecrecover(hash, vs[i], rs[i], ss[i]);
// only signatures by addresses in `addresses` are allowed
if (!addressArrayContains(allowed_signers, recovered_address)) {
return false;
}
// duplicate signatures are not allowed
if (addressArrayContains(encountered_addresses, recovered_address)) {
return false;
}
encountered_addresses[i] = recovered_address;
}
return true;
}
}
/// Library used only to test Helpers library via rpc calls
library HelpersTest {
function addressArrayContains(address[] array, address value) public pure returns (bool) {
return Helpers.addressArrayContains(array, value);
}
function uintToString(uint256 inputValue) public pure returns (string str) {
return Helpers.uintToString(inputValue);
}
function hasEnoughValidSignatures(bytes message, uint8[] vs, bytes32[] rs, bytes32[] ss, address[] addresses, uint256 requiredSignatures) public pure returns (bool) {
return Helpers.hasEnoughValidSignatures(message, vs, rs, ss, addresses, requiredSignatures);
}
}
// helpers for message signing.
// `internal` so they get compiled into contracts using them.
library MessageSigning {
function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) {
require(signature.length == 65);
bytes32 r;
bytes32 s;
bytes1 v;
// solium-disable-next-line security/no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := mload(add(signature, 0x60))
}
return ecrecover(hashMessage(message), uint8(v), r, s);
}
function hashMessage(bytes message) internal pure returns (bytes32) {
bytes memory prefix = "\x19Ethereum Signed Message:\n";
return keccak256(prefix, Helpers.uintToString(message.length), message);
}
}
/// Library used only to test MessageSigning library via rpc calls
library MessageSigningTest {
function recoverAddressFromSignedMessage(bytes signature, bytes message) public pure returns (address) {
return MessageSigning.recoverAddressFromSignedMessage(signature, message);
}
}
library Message {
// layout of message :: bytes:
// offset 0: 32 bytes :: uint256 - message length
// offset 32: 20 bytes :: address - recipient address
// offset 52: 32 bytes :: uint256 - value
// offset 84: 32 bytes :: bytes32 - transaction hash
// offset 116: 32 bytes :: uint256 - home gas price
// bytes 1 to 32 are 0 because message length is stored as little endian.
// mload always reads 32 bytes.
// so we can and have to start reading recipient at offset 20 instead of 32.
// if we were to read at 32 the address would contain part of value and be corrupted.
// when reading from offset 20 mload will read 12 zero bytes followed
// by the 20 recipient address bytes and correctly convert it into an address.
// this saves some storage/gas over the alternative solution
// which is padding address to 32 bytes and reading recipient at offset 32.
// for more details see discussion in:
// https://github.com/paritytech/parity-bridge/issues/61
function getRecipient(bytes message) internal pure returns (address) {
address recipient;
// solium-disable-next-line security/no-inline-assembly
assembly {
recipient := mload(add(message, 20))
}
return recipient;
}
function getValue(bytes message) internal pure returns (uint256) {
uint256 value;
// solium-disable-next-line security/no-inline-assembly
assembly {
value := mload(add(message, 52))
}
return value;
}
function getTransactionHash(bytes message) internal pure returns (bytes32) {
bytes32 hash;
// solium-disable-next-line security/no-inline-assembly
assembly {
hash := mload(add(message, 84))
}
return hash;
}
function getHomeGasPrice(bytes message) internal pure returns (uint256) {
uint256 gasPrice;
// solium-disable-next-line security/no-inline-assembly
assembly {
gasPrice := mload(add(message, 116))
}
return gasPrice;
}
}
/// Library used only to test Message library via rpc calls
library MessageTest {
function getRecipient(bytes message) public pure returns (address) {
return Message.getRecipient(message);
}
function getValue(bytes message) public pure returns (uint256) {
return Message.getValue(message);
}
function getTransactionHash(bytes message) public pure returns (bytes32) {
return Message.getTransactionHash(message);
}
function getHomeGasPrice(bytes message) public pure returns (uint256) {
return Message.getHomeGasPrice(message);
}
}
/// This contract introduces a new field which can be used by new bridge
/// instances to get information when the bridge contract was deployed.
/// This will avoid necessity to distribute this information as part of the
/// database file to new validators if they want to join to existing
/// bridge validators group.
/// So, now bridge deployment script or webapp could pickup HomeBridge
/// and ForeignBridge addresses and request block deployed from the contracts
/// in order to generate correct database file.
contract BridgeDeploymentAddressStorage {
uint256 public deployedAtBlock;
function BridgeDeploymentAddressStorage() public {
deployedAtBlock = block.number;
}
}
/// Due to nature of bridge operations it makes sense to have the same value
/// of gas consumption limits which will distributed among all validators serving
/// particular bridge. This approach introduces few advantages:
/// --- new bridge instances will pickup limits from the contract instead of
/// looking at the configuration file (this configuration parameters could be
/// depricated)
/// --- as soon as upgradable bridge contract is implemented these limits needs
/// to be updated every time the contract is upgraded. Validators could get
/// an event that limits updated and use new values to send transactions.
contract HomeBridgeGasConsumptionLimitsStorage {
uint256 public gasLimitWithdrawRelay;
event GasConsumptionLimitsUpdated(uint256);
function setGasLimitWithdrawRelay(uint256 gas) {
gasLimitWithdrawRelay = gas;
GasConsumptionLimitsUpdated(gasLimitWithdrawRelay);
}
}
contract ForeignBridgeGasConsumptionLimitsStorage {
uint256 public gasLimitDepositRelay;
uint256 public gasLimitWithdrawConfirm;
event GasConsumptionLimitsUpdated(uint256, uint256);
function setGasLimitDepositRelay(uint256 gas) {
gasLimitDepositRelay = gas;
GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm);
}
function setGasLimitWithdrawConfirm(uint256 gas) {
gasLimitWithdrawConfirm = gas;
GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm);
}
}
contract HomeBridge is BridgeDeploymentAddressStorage,
HomeBridgeGasConsumptionLimitsStorage {
/// Number of authorities signatures required to withdraw the money.
///
/// Must be lesser than number of authorities.
uint256 public requiredSignatures;
/// The gas cost of calling `HomeBridge.withdraw`.
///
/// Is subtracted from `value` on withdraw.
/// recipient pays the relaying authority for withdraw.
/// this shuts down attacks that exhaust authorities funds on home chain.
uint256 public estimatedGasCostOfWithdraw;
/// Contract authorities.
address[] public authorities;
/// Used foreign transaction hashes.
mapping (bytes32 => bool) withdraws;
/// Event created on money deposit.
event Deposit (address recipient, uint256 value);
/// Event created on money withdraw.
event Withdraw (address recipient, uint256 value);
/// Constructor.
function HomeBridge(
uint256 requiredSignaturesParam,
address[] authoritiesParam,
uint256 estimatedGasCostOfWithdrawParam
) public
{
require(requiredSignaturesParam != 0);
require(requiredSignaturesParam <= authoritiesParam.length);
requiredSignatures = requiredSignaturesParam;
authorities = authoritiesParam;
estimatedGasCostOfWithdraw = estimatedGasCostOfWithdrawParam;
}
/// Should be used to deposit money.
function () public payable {
Deposit(msg.sender, msg.value);
}
/// final step of a withdraw.
/// checks that `requiredSignatures` `authorities` have signed of on the `message`.
/// then transfers `value` to `recipient` (both extracted from `message`).
/// see message library above for a breakdown of the `message` contents.
/// `vs`, `rs`, `ss` are the components of the signatures.
/// anyone can call this, provided they have the message and required signatures!
/// only the `authorities` can create these signatures.
/// `requiredSignatures` authorities can sign arbitrary `message`s
/// transfering any ether `value` out of this contract to `recipient`.
/// bridge users must trust a majority of `requiredSignatures` of the `authorities`.
function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public {
require(message.length == 116);
// check that at least `requiredSignatures` `authorities` have signed `message`
require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, authorities, requiredSignatures));
address recipient = Message.getRecipient(message);
uint256 value = Message.getValue(message);
bytes32 hash = Message.getTransactionHash(message);
uint256 homeGasPrice = Message.getHomeGasPrice(message);
// if the recipient calls `withdraw` they can choose the gas price freely.
// if anyone else calls `withdraw` they have to use the gas price
// `homeGasPrice` specified by the user initiating the withdraw.
// this is a security mechanism designed to shut down
// malicious senders setting extremely high gas prices
// and effectively burning recipients withdrawn value.
// see https://github.com/paritytech/parity-bridge/issues/112
// for further explanation.
require((recipient == msg.sender) || (tx.gasprice == homeGasPrice));
// The following two statements guard against reentry into this function.
// Duplicated withdraw or reentry.
require(!withdraws[hash]);
// Order of operations below is critical to avoid TheDAO-like re-entry bug
withdraws[hash] = true;
uint256 estimatedWeiCostOfWithdraw = estimatedGasCostOfWithdraw * homeGasPrice;
// charge recipient for relay cost
uint256 valueRemainingAfterSubtractingCost = value - estimatedWeiCostOfWithdraw;
// pay out recipient
recipient.transfer(valueRemainingAfterSubtractingCost);
// refund relay cost to relaying authority
msg.sender.transfer(estimatedWeiCostOfWithdraw);
Withdraw(recipient, valueRemainingAfterSubtractingCost);
}
}
contract ERC20 {
function transfer(address to, uint256 value) public returns (bool);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function allowance(address owner, address spender) public constant returns (uint256);
function balanceOf(address tokenOwner) public constant returns (uint balance);
}
contract ForeignBridge is BridgeDeploymentAddressStorage,
ForeignBridgeGasConsumptionLimitsStorage {
/// Number of authorities signatures required to withdraw the money.
///
/// Must be less than number of authorities.
uint256 public requiredSignatures;
uint256 public estimatedGasCostOfWithdraw;
// Original parity-bridge assumes that anyone could forward final
// withdraw confirmation to the HomeBridge contract. That's why
// they need to make sure that no one is trying to steal funds by
// setting a big gas price of withdraw transaction. So,
// funds sender is responsible to limit this by setting gasprice
// as part of withdraw request.
// Since it is not the case for POA CCT bridge, gasprice is set
// to 1 Gwei which is minimal gasprice for POA network.
uint256 homeGasPrice = 1000000000 wei;
/// Contract authorities.
mapping (address => bool) authorities;
/// Pending mesages
mapping (bytes32 => bytes) messages;
/// ???
mapping (bytes32 => bytes) signatures;
/// Pending deposits and authorities who confirmed them
mapping (bytes32 => bool) messages_signed;
mapping (bytes32 => uint) num_messages_signed;
/// Pending deposits and authorities who confirmed them
mapping (bytes32 => bool) deposits_signed;
mapping (bytes32 => uint) num_deposits_signed;
/// Token to work with
ERC20 public erc20token;
/// List of authorities confirmed to set up ERC-20 token address
mapping (bytes32 => bool) tokenAddressAprroval_signs;
mapping (address => uint256) num_tokenAddressAprroval_signs;
/// triggered when relay of deposit from HomeBridge is complete
event Deposit(address recipient, uint256 value);
/// Event created on money withdraw.
event Withdraw(address recipient, uint256 value, uint256 homeGasPrice);
/// Collected signatures which should be relayed to home chain.
event CollectedSignatures(address authorityResponsibleForRelay, bytes32 messageHash, uint256 NumberOfCollectedSignatures);
/// Event created when new token address is set up.
event TokenAddress(address token);
/// Constructor.
function ForeignBridge(
uint256 _requiredSignatures,
address[] _authorities,
uint256 _estimatedGasCostOfWithdraw
) public
{
require(_requiredSignatures != 0);
require(_requiredSignatures <= _authorities.length);
requiredSignatures = _requiredSignatures;
for (uint i = 0; i < _authorities.length; i++) {
authorities[_authorities[i]] = true;
}
estimatedGasCostOfWithdraw = _estimatedGasCostOfWithdraw;
}
/// require that sender is an authority
modifier onlyAuthority() {
require(authorities[msg.sender]);
_;
}
/// Set up the token address. It allows to set up or change
/// the ERC20 token address only if authorities confirmed this.
///
/// Usage maps instead of arrey allows to reduce gas consumption
///
/// token address (address)
function setTokenAddress (ERC20 token) public onlyAuthority() {
// Duplicated deposits
bytes32 token_sender = keccak256(msg.sender, token);
require(!tokenAddressAprroval_signs[token_sender]);
tokenAddressAprroval_signs[token_sender]= true;
uint signed = num_tokenAddressAprroval_signs[address(token)] + 1;
num_tokenAddressAprroval_signs[address(token)] = signed;
// TODO: this may cause troubles if requriedSignatures len is changed
if (signed == requiredSignatures) {
erc20token = ERC20(token);
TokenAddress(token);
}
}
/// Used to transfer tokens to the `recipient`.
/// The bridge contract must own enough tokens to release them for
/// recipients. Tokens must be transfered to the bridge contract BEFORE
/// the first deposit will be performed.
///
/// Usage maps instead of array allows to reduce gas consumption
/// from 91169 to 89348 (solc 0.4.19).
///
/// deposit recipient (bytes20)
/// deposit value (uint256)
/// mainnet transaction hash (bytes32) // to avoid transaction duplication
function deposit(address recipient, uint value, bytes32 transactionHash) public onlyAuthority() {
require(erc20token != address(0x0));
// Protection from misbehaing authority
bytes32 hash_msg = keccak256(recipient, value, transactionHash);
bytes32 hash_sender = keccak256(msg.sender, hash_msg);
// Duplicated deposits
require(!deposits_signed[hash_sender]);
deposits_signed[hash_sender]= true;
uint signed = num_deposits_signed[hash_msg] + 1;
num_deposits_signed[hash_msg] = signed;
// TODO: this may cause troubles if requriedSignatures len is changed
if (signed == requiredSignatures) {
// If the bridge contract does not own enough tokens to transfer
// it will couse funds lock on the home side of the bridge
erc20token.transfer(recipient, value);
Deposit(recipient, value);
}
}
/// Used to transfer `value` of tokens from `_from`s balance on local
/// (`foreign`) chain to the same address (`_from`) on `home` chain.
/// Transfer of tokens within local (`foreign`) chain performed by usual
/// way through transfer method of the token contract.
/// In order to swap tokens to coins the owner (`_from`) must allow this
/// explicitly in the token contract by calling approveAndCall with address
/// of the bridge account.
/// The method locks tokens and emits a `Withdraw` event which will be
/// picked up by the bridge authorities.
/// Bridge authorities will then sign off (by calling `submitSignature`) on
/// a message containing `value`, the recipient (`_from`) and the `hash` of
/// the transaction on `foreign` containing the `Withdraw` event.
/// Once `requiredSignatures` are collected a `CollectedSignatures` event
/// will be emitted.
/// An authority will pick up `CollectedSignatures` an call
/// `HomeBridge.withdraw` which transfers `value - relayCost` to the
/// recipient completing the transfer.
function receiveApproval(address _from, uint256 _value, ERC20 _tokenContract, bytes _msg) external returns(bool) {
require(erc20token != address(0x0));
require(msg.sender == address(erc20token));
require(erc20token.allowance(_from, this) >= _value);
erc20token.transferFrom(_from, this, _value);
Withdraw(_from, _value, homeGasPrice);
return true;
}
/// Should be used as sync tool
///
/// Message is a message that should be relayed to main chain once authorities sign it.
///
/// Usage several maps instead of structure allows to reduce gas consumption
/// from 265102 to 242334 (solc 0.4.19).
///
/// for withdraw message contains:
/// withdrawal recipient (bytes20)
/// withdrawal value (uint256)
/// foreign transaction hash (bytes32) // to avoid transaction duplication
function submitSignature(bytes signature, bytes message) public onlyAuthority() {
// ensure that `signature` is really `message` signed by `msg.sender`
require(msg.sender == MessageSigning.recoverAddressFromSignedMessage(signature, message));
require(message.length == 116);
bytes32 hash = keccak256(message);
bytes32 hash_sender = keccak256(msg.sender, hash);
uint signed = num_messages_signed[hash_sender] + 1;
if (signed > 1) {
// Duplicated signatures
require(!messages_signed[hash_sender]);
}
else {
// check if it will really reduce gas usage in case of the second transaction
// with the same hash
messages[hash] = message;
}
messages_signed[hash_sender] = true;
bytes32 sign_idx = keccak256(hash, (signed-1));
signatures[sign_idx]= signature;
num_messages_signed[hash_sender] = signed;
// TODO: this may cause troubles if requiredSignatures len is changed
if (signed == requiredSignatures) {
CollectedSignatures(msg.sender, hash, signed);
}
}
/// Get signature
function signature(bytes32 hash, uint index) public view returns (bytes) {
bytes32 sign_idx = keccak256(hash, index);
return signatures[sign_idx];
}
/// Get message
function message(bytes32 hash) public view returns (bytes) {
return messages[hash];
}
}