-
Notifications
You must be signed in to change notification settings - Fork 0
/
RBN_source.sol
1839 lines (1583 loc) · 67.7 KB
/
RBN_source.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
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
pragma solidity ^0.5.10;
/** @title A contract for generating unique identifiers
*
* @notice A contract that provides an identifier generation scheme,
* guaranteeing uniqueness across all contracts that inherit from it,
* as well as the unpredictability of future identifiers.
*
* @dev This contract is intended to be inherited by any contract that
* implements the callback software pattern for cooperative custodianship.
*
*/
contract LockRequestable {
// MEMBERS
/// @notice the count of all invocations of `generateLockId`.
uint256 public lockRequestCount;
// CONSTRUCTOR
constructor() public {
lockRequestCount = 0;
}
// FUNCTIONS
/** @notice Returns a fresh unique identifier.
*
* @dev the generation scheme uses three components.
* First, the blockhash of the previous block.
* Second, the deployed address.
* Third, the next value of the counter.
* This ensures that identifiers are unique across all contracts
* following this scheme, and that future identifiers are
* unpredictable.
*
* @return a 32-byte unique identifier.
*/
function generateLockId() internal returns (bytes32 lockId) {
return keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), ++lockRequestCount));
}
}
contract ERC20Interface {
// METHODS
// NOTE:
// public getter functions are not currently recognised as an
// implementation of the matching abstract function by the compiler.
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#name
// function name() public view returns (string);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol
// function symbol() public view returns (string);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply
// function decimals() public view returns (uint8);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#totalsupply
function totalSupply() public view returns (uint256);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#balanceof
function balanceOf(address _owner) public view returns (uint256 balance);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer
function transfer(address _to, uint256 _value) public returns (bool success);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transferfrom
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approve
function approve(address _spender, uint256 _value) public returns (bool success);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#allowance
function allowance(address _owner, address _spender) public view returns (uint256 remaining);
// EVENTS
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#transfer-1
event Transfer(address indexed _from, address indexed _to, uint256 _value);
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approval
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
/** @title A dual control contract.
*
* @notice A general-purpose contract that implements dual control over
* co-operating contracts through a callback mechanism.
*
* @dev This contract implements dual control through a 2-of-N
* threshold multi-signature scheme. The contract recognizes a set of N signers,
* and will unlock requests with signatures from any distinct pair of them.
* This contract signals the unlocking through a co-operative callback
* scheme.
* This contract also provides time lock and revocation features.
* Requests made by a 'primary' account have a default time lock applied.
* All other requests must pay a 1 ETH stake and have an extended time lock
* applied.
* A request that is completed will prevent all previous pending requests
* that share the same callback from being completed: this is the
* revocation feature.
*
*/
contract Custodian {
// TYPES
/** @dev The `Request` struct stores a pending unlocking.
* `callbackAddress` and `callbackSelector` are the data required to
* make a callback. The custodian completes the process by
* calling `callbackAddress.call(callbackSelector, lockId)`, which
* signals to the contract co-operating with the Custodian that
* the 2-of-N signatures have been provided and verified.
*/
struct Request {
bytes32 lockId;
bytes4 callbackSelector; // bytes4 and address can be packed into 1 word
address callbackAddress;
uint256 idx;
uint256 timestamp;
bool extended;
}
// EVENTS
/// @dev Emitted by successful `requestUnlock` calls.
event Requested(
bytes32 _lockId,
address _callbackAddress,
bytes4 _callbackSelector,
uint256 _nonce,
address _whitelistedAddress,
bytes32 _requestMsgHash,
uint256 _timeLockExpiry
);
/// @dev Emitted by `completeUnlock` calls on requests in the time-locked state.
event TimeLocked(
uint256 _timeLockExpiry,
bytes32 _requestMsgHash
);
/// @dev Emitted by successful `completeUnlock` calls.
event Completed(
bytes32 _lockId,
bytes32 _requestMsgHash,
address _signer1,
address _signer2
);
/// @dev Emitted by `completeUnlock` calls where the callback failed.
event Failed(
bytes32 _lockId,
bytes32 _requestMsgHash,
address _signer1,
address _signer2
);
/// @dev Emitted by successful `extendRequestTimeLock` calls.
event TimeLockExtended(
uint256 _timeLockExpiry,
bytes32 _requestMsgHash
);
// MEMBERS
/** @dev The count of all requests.
* This value is used as a nonce, incorporated into the request hash.
*/
uint256 public requestCount;
/// @dev The set of signers: signatures from two signers unlock a pending request.
mapping (address => bool) public signerSet;
/// @dev The map of request hashes to pending requests.
mapping (bytes32 => Request) public requestMap;
/// @dev The map of callback addresses to callback selectors to request indexes.
mapping (address => mapping (bytes4 => uint256)) public lastCompletedIdxs;
/** @dev The default period (in seconds) to time-lock requests.
* All requests will be subject to this default time lock, and the duration
* is fixed at contract creation.
*/
uint256 public defaultTimeLock;
/** @dev The extended period (in seconds) to time-lock requests.
* Requests not from the primary account are subject to this time lock.
* The primary account may also elect to extend the time lock on requests
* that originally received the default.
*/
uint256 public extendedTimeLock;
/// @dev The primary account is the privileged account for making requests.
address public primary;
// CONSTRUCTOR
constructor(
address[] memory _signers,
uint256 _defaultTimeLock,
uint256 _extendedTimeLock,
address _primary
)
public
{
// check for at least two `_signers`
require(_signers.length >= 2, "at least two `_signers`");
// validate time lock params
require(_defaultTimeLock <= _extendedTimeLock, "valid timelock params");
defaultTimeLock = _defaultTimeLock;
extendedTimeLock = _extendedTimeLock;
primary = _primary;
// explicitly initialize `requestCount` to zero
requestCount = 0;
// turn the array into a set
for (uint i = 0; i < _signers.length; i++) {
// no zero addresses or duplicates
require(_signers[i] != address(0) && !signerSet[_signers[i]], "no zero addresses or duplicates");
signerSet[_signers[i]] = true;
}
}
// MODIFIERS
modifier onlyPrimary {
require(msg.sender == primary, "only primary");
_;
}
modifier onlySigner {
require(signerSet[msg.sender], "only signer");
_;
}
// METHODS
/** @notice Requests an unlocking with a lock identifier and a callback.
*
* @dev If called by an account other than the primary a 1 ETH stake
* must be paid. When the request is unlocked stake will be transferred to the message sender.
* This is an anti-spam measure. As well as the callback
* and the lock identifier parameters a 'whitelisted address' is required
* for compatibility with existing signature schemes.
*
* @param _lockId The identifier of a pending request in a co-operating contract.
* @param _callbackAddress The address of a co-operating contract.
* @param _callbackSelector The function selector of a function within
* the co-operating contract at address `_callbackAddress`.
* @param _whitelistedAddress An address whitelisted in existing
* offline control protocols.
*
* @return requestMsgHash The hash of a request message to be signed.
*/
function requestUnlock(
bytes32 _lockId,
address _callbackAddress,
bytes4 _callbackSelector,
address _whitelistedAddress
)
public
payable
returns (bytes32 requestMsgHash)
{
require(msg.sender == primary || msg.value >= 1 ether, "sender is primary or stake is paid");
// disallow using a zero value for the callback address
require(_callbackAddress != address(0), "no zero value for callback address");
uint256 requestIdx = ++requestCount;
// compute a nonce value
// - the blockhash prevents prediction of future nonces
// - the address of this contract prevents conflicts with co-operating contracts using this scheme
// - the counter prevents conflicts arising from multiple txs within the same block
uint256 nonce = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), address(this), requestIdx)));
requestMsgHash = keccak256(
abi.encodePacked(
nonce,
_whitelistedAddress,
uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
)
);
requestMap[requestMsgHash] = Request({
lockId: _lockId,
callbackSelector: _callbackSelector,
callbackAddress: _callbackAddress,
idx: requestIdx,
timestamp: block.timestamp,
extended: false
});
// compute the expiry time
uint256 timeLockExpiry = block.timestamp;
if (msg.sender == primary) {
timeLockExpiry += defaultTimeLock;
} else {
timeLockExpiry += extendedTimeLock;
// any sender that is not the creator will get the extended time lock
requestMap[requestMsgHash].extended = true;
}
emit Requested(_lockId, _callbackAddress, _callbackSelector, nonce, _whitelistedAddress, requestMsgHash, timeLockExpiry);
}
/** @notice Completes a pending unlocking with two signatures.
*
* @dev Given a request message hash as two signatures of it from
* two distinct signers in the signer set, this function completes the
* unlocking of the pending request by executing the callback.
*
* @param _requestMsgHash The request message hash of a pending request.
* @param _recoveryByte1 The public key recovery byte (27 or 28)
* @param _ecdsaR1 The R component of an ECDSA signature (R, S) pair
* @param _ecdsaS1 The S component of an ECDSA signature (R, S) pair
* @param _recoveryByte2 The public key recovery byte (27 or 28)
* @param _ecdsaR2 The R component of an ECDSA signature (R, S) pair
* @param _ecdsaS2 The S component of an ECDSA signature (R, S) pair
*
* @return success True if the callback successfully executed.
*/
function completeUnlock(
bytes32 _requestMsgHash,
uint8 _recoveryByte1, bytes32 _ecdsaR1, bytes32 _ecdsaS1,
uint8 _recoveryByte2, bytes32 _ecdsaR2, bytes32 _ecdsaS2
)
public
onlySigner
returns (bool success)
{
Request storage request = requestMap[_requestMsgHash];
// copy storage to locals before `delete`
bytes32 lockId = request.lockId;
address callbackAddress = request.callbackAddress;
bytes4 callbackSelector = request.callbackSelector;
// failing case of the lookup if the callback address is zero
require(callbackAddress != address(0), "no zero value for callback address");
// reject confirms of earlier withdrawals buried under later confirmed withdrawals
require(request.idx > lastCompletedIdxs[callbackAddress][callbackSelector],
"reject confirms of earlier withdrawals buried under later confirmed withdrawals");
address signer1 = ecrecover(
keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _requestMsgHash)),
_recoveryByte1,
_ecdsaR1,
_ecdsaS1
);
require(signerSet[signer1], "signer is set");
address signer2 = ecrecover(
keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _requestMsgHash)),
_recoveryByte2,
_ecdsaR2,
_ecdsaS2
);
require(signerSet[signer2], "signer is set");
require(signer1 != signer2, "signers are different");
if (request.extended && ((block.timestamp - request.timestamp) < extendedTimeLock)) {
emit TimeLocked(request.timestamp + extendedTimeLock, _requestMsgHash);
return false;
} else if ((block.timestamp - request.timestamp) < defaultTimeLock) {
emit TimeLocked(request.timestamp + defaultTimeLock, _requestMsgHash);
return false;
} else {
if (address(this).balance > 0) {
// reward sender with anti-spam payments
msg.sender.transfer(address(this).balance);
}
// raise the waterline for the last completed unlocking
lastCompletedIdxs[callbackAddress][callbackSelector] = request.idx;
// and delete the request
delete requestMap[_requestMsgHash];
// invoke callback
(success,) = callbackAddress.call(abi.encodeWithSelector(callbackSelector, lockId));
if (success) {
emit Completed(lockId, _requestMsgHash, signer1, signer2);
} else {
emit Failed(lockId, _requestMsgHash, signer1, signer2);
}
}
}
/** @notice Reclaim the storage of a pending request that is uncompletable.
*
* @dev If a pending request shares the callback (address and selector) of
* a later request has been completed, then the request can no longer
* be completed. This function will reclaim the contract storage of the
* pending request.
*
* @param _requestMsgHash The request message hash of a pending request.
*/
function deleteUncompletableRequest(bytes32 _requestMsgHash) public {
Request storage request = requestMap[_requestMsgHash];
uint256 idx = request.idx;
require(0 < idx && idx < lastCompletedIdxs[request.callbackAddress][request.callbackSelector],
"there must be a completed latter request with same callback");
delete requestMap[_requestMsgHash];
}
/** @notice Extend the time lock of a pending request.
*
* @dev Requests made by the primary account receive the default time lock.
* This function allows the primary account to apply the extended time lock
* to one its own requests.
*
* @param _requestMsgHash The request message hash of a pending request.
*/
function extendRequestTimeLock(bytes32 _requestMsgHash) public onlyPrimary {
Request storage request = requestMap[_requestMsgHash];
// reject ‘null’ results from the map lookup
// this can only be the case if an unknown `_requestMsgHash` is received
require(request.callbackAddress != address(0), "reject ‘null’ results from the map lookup");
// `extendRequestTimeLock` must be idempotent
require(request.extended != true, "`extendRequestTimeLock` must be idempotent");
// set the `extended` flag; note that this is never unset
request.extended = true;
emit TimeLockExtended(request.timestamp + extendedTimeLock, _requestMsgHash);
}
}
/** @title A contract to inherit upgradeable custodianship.
*
* @notice A contract that provides re-usable code for upgradeable
* custodianship. That custodian may be an account or another contract.
*
* @dev This contract is intended to be inherited by any contract
* requiring a custodian to control some aspect of its functionality.
* This contract provides the mechanism for that custodianship to be
* passed from one custodian to the next.
*
*/
contract CustodianUpgradeable is LockRequestable {
// TYPES
/// @dev The struct type for pending custodian changes.
struct CustodianChangeRequest {
address proposedNew;
}
// MEMBERS
/// @dev The address of the account or contract that acts as the custodian.
address public custodian;
/// @dev The map of lock ids to pending custodian changes.
mapping (bytes32 => CustodianChangeRequest) public custodianChangeReqs;
// CONSTRUCTOR
constructor(
address _custodian
)
LockRequestable()
public
{
custodian = _custodian;
}
// MODIFIERS
modifier onlyCustodian {
require(msg.sender == custodian, "only custodian");
_;
}
// PUBLIC FUNCTIONS
// (UPGRADE)
/** @notice Requests a change of the custodian associated with this contract.
*
* @dev Returns a unique lock id associated with the request.
* Anyone can call this function, but confirming the request is authorized
* by the custodian.
*
* @param _proposedCustodian The address of the new custodian.
* @return lockId A unique identifier for this request.
*/
function requestCustodianChange(address _proposedCustodian) public returns (bytes32 lockId) {
require(_proposedCustodian != address(0), "no null value for `_proposedCustodian`");
lockId = generateLockId();
custodianChangeReqs[lockId] = CustodianChangeRequest({
proposedNew: _proposedCustodian
});
emit CustodianChangeRequested(lockId, msg.sender, _proposedCustodian);
}
/** @notice Confirms a pending change of the custodian associated with this contract.
*
* @dev When called by the current custodian with a lock id associated with a
* pending custodian change, the `address custodian` member will be updated with the
* requested address.
*
* @param _lockId The identifier of a pending change request.
*/
function confirmCustodianChange(bytes32 _lockId) public onlyCustodian {
custodian = getCustodianChangeReq(_lockId);
delete custodianChangeReqs[_lockId];
emit CustodianChangeConfirmed(_lockId, custodian);
}
// PRIVATE FUNCTIONS
function getCustodianChangeReq(bytes32 _lockId) private view returns (address _proposedNew) {
CustodianChangeRequest storage changeRequest = custodianChangeReqs[_lockId];
// reject ‘null’ results from the map lookup
// this can only be the case if an unknown `_lockId` is received
require(changeRequest.proposedNew != address(0), "reject ‘null’ results from the map lookup");
return changeRequest.proposedNew;
}
//EVENTS
/// @dev Emitted by successful `requestCustodianChange` calls.
event CustodianChangeRequested(
bytes32 _lockId,
address _msgSender,
address _proposedCustodian
);
/// @dev Emitted by successful `confirmCustodianChange` calls.
event CustodianChangeConfirmed(bytes32 _lockId, address _newCustodian);
}
/** @title A contract to inherit upgradeable token implementations.
*
* @notice A contract that provides re-usable code for upgradeable
* token implementations. It itself inherits from `CustodianUpgradable`
* as the upgrade process is controlled by the custodian.
*
* @dev This contract is intended to be inherited by any contract
* requiring a reference to the active token implementation, either
* to delegate calls to it, or authorize calls from it. This contract
* provides the mechanism for that implementation to be replaced,
* which constitutes an implementation upgrade.
*
*/
contract ERC20ImplUpgradeable is CustodianUpgradeable {
// TYPES
/// @dev The struct type for pending implementation changes.
struct ImplChangeRequest {
address proposedNew;
}
// MEMBERS
// @dev The reference to the active token implementation.
ERC20Impl public erc20Impl;
/// @dev The map of lock ids to pending implementation changes.
mapping (bytes32 => ImplChangeRequest) public implChangeReqs;
// CONSTRUCTOR
constructor(address _custodian) CustodianUpgradeable(_custodian) public {
erc20Impl = ERC20Impl(0x0);
}
// MODIFIERS
modifier onlyImpl {
require(msg.sender == address(erc20Impl), "only ERC20Impl");
_;
}
// PUBLIC FUNCTIONS
// (UPGRADE)
/** @notice Requests a change of the active implementation associated
* with this contract.
*
* @dev Returns a unique lock id associated with the request.
* Anyone can call this function, but confirming the request is authorized
* by the custodian.
*
* @param _proposedImpl The address of the new active implementation.
* @return lockId A unique identifier for this request.
*/
function requestImplChange(address _proposedImpl) public returns (bytes32 lockId) {
require(_proposedImpl != address(0), "no null value for `_proposedImpl`");
lockId = generateLockId();
implChangeReqs[lockId] = ImplChangeRequest({
proposedNew: _proposedImpl
});
emit ImplChangeRequested(lockId, msg.sender, _proposedImpl);
}
/** @notice Confirms a pending change of the active implementation
* associated with this contract.
*
* @dev When called by the custodian with a lock id associated with a
* pending change, the `ERC20Impl erc20Impl` member will be updated
* with the requested address.
*
* @param _lockId The identifier of a pending change request.
*/
function confirmImplChange(bytes32 _lockId) public onlyCustodian {
erc20Impl = getImplChangeReq(_lockId);
delete implChangeReqs[_lockId];
emit ImplChangeConfirmed(_lockId, address(erc20Impl));
}
// PRIVATE FUNCTIONS
function getImplChangeReq(bytes32 _lockId) private view returns (ERC20Impl _proposedNew) {
ImplChangeRequest storage changeRequest = implChangeReqs[_lockId];
// reject ‘null’ results from the map lookup
// this can only be the case if an unknown `_lockId` is received
require(changeRequest.proposedNew != address(0), "reject ‘null’ results from the map lookup");
return ERC20Impl(changeRequest.proposedNew);
}
//EVENTS
/// @dev Emitted by successful `requestImplChange` calls.
event ImplChangeRequested(
bytes32 _lockId,
address _msgSender,
address _proposedImpl
);
/// @dev Emitted by successful `confirmImplChange` calls.
event ImplChangeConfirmed(bytes32 _lockId, address _newImpl);
}
/** @title Public interface to ERC20 compliant token.
*
* @notice This contract is a permanent entry point to an ERC20 compliant
* system of contracts.
*
* @dev This contract contains no business logic and instead
* delegates to an instance of ERC20Impl. This contract also has no storage
* that constitutes the operational state of the token. This contract is
* upgradeable in the sense that the `custodian` can update the
* `erc20Impl` address, thus redirecting the delegation of business logic.
* The `custodian` is also authorized to pass custodianship.
*
*/
contract ERC20Proxy is ERC20Interface, ERC20ImplUpgradeable {
// MEMBERS
/// @notice Returns the name of the token.
string public name;
/// @notice Returns the symbol of the token.
string public symbol;
/// @notice Returns the number of decimals the token uses.
uint8 public decimals;
// CONSTRUCTOR
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals,
address _custodian
)
ERC20ImplUpgradeable(_custodian)
public
{
name = _name;
symbol = _symbol;
decimals = _decimals;
}
// PUBLIC FUNCTIONS
// (ERC20Interface)
/** @notice Returns the total token supply.
*
* @return the total token supply.
*/
function totalSupply() public view returns (uint256) {
return erc20Impl.totalSupply();
}
/** @notice Returns the account balance of another account with an address
* `_owner`.
*
* @return balance the balance of account with address `_owner`.
*/
function balanceOf(address _owner) public view returns (uint256 balance) {
return erc20Impl.balanceOf(_owner);
}
/** @dev Internal use only.
*/
function emitTransfer(address _from, address _to, uint256 _value) public onlyImpl {
emit Transfer(_from, _to, _value);
}
/** @notice Transfers `_value` amount of tokens to address `_to`.
*
* @dev Will fire the `Transfer` event. Will revert if the `_from`
* account balance does not have enough tokens to spend.
*
* @return success true if transfer completes.
*/
function transfer(address _to, uint256 _value) public returns (bool success) {
return erc20Impl.transferWithSender(msg.sender, _to, _value);
}
/** @notice Transfers `_value` amount of tokens from address `_from`
* to address `_to`.
*
* @dev Will fire the `Transfer` event. Will revert unless the `_from`
* account has deliberately authorized the sender of the message
* via some mechanism.
*
* @return success true if transfer completes.
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
return erc20Impl.transferFromWithSender(msg.sender, _from, _to, _value);
}
/** @dev Internal use only.
*/
function emitApproval(address _owner, address _spender, uint256 _value) public onlyImpl {
emit Approval(_owner, _spender, _value);
}
/** @notice Allows `_spender` to withdraw from your account multiple times,
* up to the `_value` amount. If this function is called again it
* overwrites the current allowance with _value.
*
* @dev Will fire the `Approval` event.
*
* @return success true if approval completes.
*/
function approve(address _spender, uint256 _value) public returns (bool success) {
return erc20Impl.approveWithSender(msg.sender, _spender, _value);
}
/** @notice Increases the amount `_spender` is allowed to withdraw from
* your account.
* This function is implemented to avoid the race condition in standard
* ERC20 contracts surrounding the `approve` method.
*
* @dev Will fire the `Approval` event. This function should be used instead of
* `approve`.
*
* @return success true if approval completes.
*/
function increaseApproval(address _spender, uint256 _addedValue) public returns (bool success) {
return erc20Impl.increaseApprovalWithSender(msg.sender, _spender, _addedValue);
}
/** @notice Decreases the amount `_spender` is allowed to withdraw from
* your account. This function is implemented to avoid the race
* condition in standard ERC20 contracts surrounding the `approve` method.
*
* @dev Will fire the `Approval` event. This function should be used
* instead of `approve`.
*
* @return success true if approval completes.
*/
function decreaseApproval(address _spender, uint256 _subtractedValue) public returns (bool success) {
return erc20Impl.decreaseApprovalWithSender(msg.sender, _spender, _subtractedValue);
}
/** @notice Returns how much `_spender` is currently allowed to spend from
* `_owner`'s balance.
*
* @return remaining the remaining allowance.
*/
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return erc20Impl.allowance(_owner, _spender);
}
}
/** @title ERC20 compliant token balance store.
*
* @notice This contract serves as the store of balances, allowances, and
* supply for the ERC20 compliant token. No business logic exists here.
*
* @dev This contract contains no business logic and instead
* is the final destination for any change in balances, allowances, or token
* supply. This contract is upgradeable in the sense that its custodian can
* update the `erc20Impl` address, thus redirecting the source of logic that
* determines how the balances will be updated.
*
*/
contract ERC20Store is ERC20ImplUpgradeable {
// MEMBERS
/// @dev The total token supply.
uint256 public totalSupply;
/// @dev The mapping of balances.
mapping (address => uint256) public balances;
/// @dev The mapping of allowances.
mapping (address => mapping (address => uint256)) public allowed;
// CONSTRUCTOR
constructor(address _custodian) ERC20ImplUpgradeable(_custodian) public {
totalSupply = 0;
}
// PUBLIC FUNCTIONS
// (ERC20 Ledger)
/** @notice The function to set the total supply of tokens.
*
* @dev Intended for use by token implementation functions
* that update the total supply. The only authorized caller
* is the active implementation.
*
* @param _newTotalSupply the value to set as the new total supply
*/
function setTotalSupply(
uint256 _newTotalSupply
)
public
onlyImpl
{
totalSupply = _newTotalSupply;
}
/** @notice Sets how much `_owner` allows `_spender` to transfer on behalf
* of `_owner`.
*
* @dev Intended for use by token implementation functions
* that update spending allowances. The only authorized caller
* is the active implementation.
*
* @param _owner The account that will allow an on-behalf-of spend.
* @param _spender The account that will spend on behalf of the owner.
* @param _value The limit of what can be spent.
*/
function setAllowance(
address _owner,
address _spender,
uint256 _value
)
public
onlyImpl
{
allowed[_owner][_spender] = _value;
}
/** @notice Sets the balance of `_owner` to `_newBalance`.
*
* @dev Intended for use by token implementation functions
* that update balances. The only authorized caller
* is the active implementation.
*
* @param _owner The account that will hold a new balance.
* @param _newBalance The balance to set.
*/
function setBalance(
address _owner,
uint256 _newBalance
)
public
onlyImpl
{
balances[_owner] = _newBalance;
}
/** @notice Adds `_balanceIncrease` to `_owner`'s balance.
*
* @dev Intended for use by token implementation functions
* that update balances. The only authorized caller
* is the active implementation.
* WARNING: the caller is responsible for preventing overflow.
*
* @param _owner The account that will hold a new balance.
* @param _balanceIncrease The balance to add.
*/
function addBalance(
address _owner,
uint256 _balanceIncrease
)
public
onlyImpl
{
balances[_owner] = balances[_owner] + _balanceIncrease;
}
}
/** @title ERC20 compliant token intermediary contract holding core logic.
*
* @notice This contract serves as an intermediary between the exposed ERC20
* interface in ERC20Proxy and the store of balances in ERC20Store. This
* contract contains core logic that the proxy can delegate to
* and that the store is called by.
*
* @dev This contract contains the core logic to implement the
* ERC20 specification as well as several extensions.
* 1. Changes to the token supply.
* 2. Batched transfers.
* 3. Relative changes to spending approvals.
* 4. Delegated transfer control ('sweeping').
*
*/
contract ERC20Impl is CustodianUpgradeable {
// TYPES
/// @dev The struct type for pending increases to the token supply (print).
struct PendingPrint {
address receiver;
uint256 value;
}
// MEMBERS
/// @dev The reference to the proxy.
ERC20Proxy public erc20Proxy;
/// @dev The reference to the store.
ERC20Store public erc20Store;
/// @dev The sole authorized caller of delegated transfer control ('sweeping').
address public sweeper;
/** @dev The static message to be signed by an external account that
* signifies their permission to forward their balance to any arbitrary
* address. This is used to consolidate the control of all accounts
* backed by a shared keychain into the control of a single key.
* Initialized as the concatenation of the address of this contract
* and the word "sweep". This concatenation is done to prevent a replay
* attack in a subsequent contract, where the sweeping message could
* potentially be replayed to re-enable sweeping ability.
*/
bytes32 public sweepMsg;
/** @dev The mapping that stores whether the address in question has
* enabled sweeping its contents to another account or not.
* If an address maps to "true", it has already enabled sweeping,
* and thus does not need to re-sign the `sweepMsg` to enact the sweep.
*/
mapping (address => bool) public sweptSet;
/// @dev The map of lock ids to pending token increases.
mapping (bytes32 => PendingPrint) public pendingPrintMap;
/// @dev The map of blocked addresses.
mapping (address => bool) public blocked;
// CONSTRUCTOR
constructor(
address _erc20Proxy,
address _erc20Store,
address _custodian,
address _sweeper
)
CustodianUpgradeable(_custodian)
public
{
require(_sweeper != address(0), "no null value for `_sweeper`");
erc20Proxy = ERC20Proxy(_erc20Proxy);
erc20Store = ERC20Store(_erc20Store);
sweeper = _sweeper;
sweepMsg = keccak256(abi.encodePacked(address(this), "sweep"));
}
// MODIFIERS
modifier onlyProxy {
require(msg.sender == address(erc20Proxy), "only ERC20Proxy");
_;
}
modifier onlySweeper {
require(msg.sender == sweeper, "only sweeper");
_;
}
/** @notice Core logic of the ERC20 `approve` function.
*
* @dev This function can only be called by the referenced proxy,
* which has an `approve` function.
* Every argument passed to that function as well as the original
* `msg.sender` gets passed to this function.
* NOTE: approvals for the zero address (unspendable) are disallowed.
*
* @param _sender The address initiating the approval in a proxy.
*/
function approveWithSender(
address _sender,
address _spender,
uint256 _value
)
public
onlyProxy
returns (bool success)