-
Notifications
You must be signed in to change notification settings - Fork 20
/
EthereumVaultConnector.sol
1236 lines (1018 loc) · 53.2 KB
/
EthereumVaultConnector.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
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.19;
import {Set, SetStorage} from "./Set.sol";
import {Events} from "./Events.sol";
import {Errors} from "./Errors.sol";
import {ExecutionContext, EC} from "./ExecutionContext.sol";
import {TransientStorage} from "./TransientStorage.sol";
import {IEVC} from "./interfaces/IEthereumVaultConnector.sol";
import {IVault} from "./interfaces/IVault.sol";
import {IERC1271} from "./interfaces/IERC1271.sol";
/// @title EthereumVaultConnector
/// @custom:security-contact security@euler.xyz
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice This contract implements the Ethereum Vault Connector.
contract EthereumVaultConnector is Events, Errors, TransientStorage, IEVC {
using ExecutionContext for EC;
using Set for SetStorage;
///////////////////////////////////////////////////////////////////////////////////////////////
// CONSTANTS //
///////////////////////////////////////////////////////////////////////////////////////////////
/// @notice Name of the Ethereum Vault Connector.
string public constant name = "Ethereum Vault Connector";
/// @notice Version of the Ethereum Vault Connector.
string public constant version = "1";
uint160 internal constant ACCOUNT_ID_OFFSET = 8;
bytes32 internal constant HASHED_NAME = keccak256(bytes(name));
bytes32 internal constant HASHED_VERSION = keccak256(bytes(version));
bytes32 internal constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 internal constant PERMIT_TYPEHASH = keccak256(
"Permit(address signer,address sender,uint256 nonceNamespace,uint256 nonce,uint256 deadline,uint256 value,bytes data)"
);
uint256 internal immutable CACHED_CHAIN_ID;
bytes32 internal immutable CACHED_DOMAIN_SEPARATOR;
///////////////////////////////////////////////////////////////////////////////////////////////
// STORAGE //
///////////////////////////////////////////////////////////////////////////////////////////////
// EVC implements controller isolation, meaning that unless in transient state, only one controller per account can
// be enabled. However, this can lead to a suboptimal user experience. In the event a user wants to have multiple
// controllers enabled, a separate wallet must be created and funded. Although there is nothing wrong with having
// many accounts within the same wallet, this can be a bad experience. In order to improve on this, EVC supports
// the concept of an owner that owns 256 accounts within EVC.
// Every Ethereum address has 256 accounts in the EVC (including the primary account - called the owner).
// Each account has an account ID from 0-255, where 0 is the owner account's ID. In order to compute the account
// addresses, the account ID is treated as a uint256 and XORed (exclusive ORed) with the Ethereum address.
// In order to record the owner of a group of 256 accounts, the EVC uses a definition of an address prefix.
// An address prefix is a part of an address having the first 19 bytes common with any of the 256 account
// addresses belonging to the same group.
// account/152 -> prefix/152
// To get an address prefix for the account, it's enough to take the account address and right shift it by 8 bits.
// Yes, this reduces the security of addresses by 8 bits, but creating multiple addresses in the wallet also reduces
// security: if somebody is trying to brute-force one of user's N>1 private keys, they have N times as many chances
// of succeeding per guess. It has to be admitted that the EVC model is weaker because finding a private key for
// an owner gives access to all accounts, but there is still a very comfortable security margin.
// Internal data structure that stores the addressPrefix owner and mode flags
struct OwnerStorage {
// The addressPrefix owner
address owner;
// Flag indicating if the addressPrefix is in lockdown mode
bool isLockdownMode;
// Flag indicating if the permit function is disabled for the addressPrefix
bool isPermitDisabledMode;
}
mapping(bytes19 addressPrefix => OwnerStorage) internal ownerLookup;
mapping(bytes19 addressPrefix => mapping(address operator => uint256 operatorBitField)) internal operatorLookup;
mapping(bytes19 addressPrefix => mapping(uint256 nonceNamespace => uint256 nonce)) internal nonceLookup;
mapping(address account => SetStorage) internal accountCollaterals;
mapping(address account => SetStorage) internal accountControllers;
///////////////////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR, FALLBACKS //
///////////////////////////////////////////////////////////////////////////////////////////////
constructor() {
CACHED_CHAIN_ID = block.chainid;
CACHED_DOMAIN_SEPARATOR = calculateDomainSeparator();
}
/// @notice Fallback function to receive Ether.
receive() external payable {
// only allows to receive value when checks are deferred
if (!executionContext.areChecksDeferred()) {
revert EVC_NotAuthorized();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// MODIFIERS //
///////////////////////////////////////////////////////////////////////////////////////////////
/// @notice A modifier that allows only the address recorded as an owner of the address prefix to call the function.
/// @dev The owner of an address prefix is an address that matches the address that has previously been recorded (or
/// will be) as an owner in the ownerLookup.
/// @param addressPrefix The address prefix for which it is checked whether the caller is the owner.
modifier onlyOwner(bytes19 addressPrefix) {
authenticateCaller({addressPrefix: addressPrefix, allowOperator: false, checkLockdownMode: false});
_;
}
/// @notice A modifier that allows only the owner or an operator of the account to call the function.
/// @dev The owner of an address prefix is an address that matches the address that has previously been recorded (or
/// will be) as an owner in the ownerLookup. An operator of an account is an address that has been authorized by the
/// owner of an account to perform operations on behalf of the owner.
/// @param account The address of the account for which it is checked whether the caller is the owner or an
/// operator.
modifier onlyOwnerOrOperator(address account) {
authenticateCaller({account: account, allowOperator: true, checkLockdownMode: true});
_;
}
/// @notice A modifier checks whether msg.sender is the only controller for the account.
/// @dev The controller cannot use permit function in conjunction with this modifier.
modifier onlyController(address account) {
{
uint256 numOfControllers = accountControllers[account].numElements;
address controller = accountControllers[account].firstElement;
if (numOfControllers != 1) {
revert EVC_ControllerViolation();
}
if (controller != msg.sender) {
revert EVC_NotAuthorized();
}
}
_;
}
/// @notice A modifier that verifies whether account or vault status checks are re-entered.
modifier nonReentrantChecks() {
if (executionContext.areChecksInProgress()) {
revert EVC_ChecksReentrancy();
}
_;
}
/// @notice A modifier that verifies whether account or vault status checks are re-entered as well as checks for
/// controlCollateral re-entrancy.
modifier nonReentrantChecksAndControlCollateral() {
{
EC context = executionContext;
if (context.areChecksInProgress()) {
revert EVC_ChecksReentrancy();
}
if (context.isControlCollateralInProgress()) {
revert EVC_ControlCollateralReentrancy();
}
}
_;
}
/// @notice A modifier that verifies whether account or vault status checks are re-entered and sets the lock.
/// @dev This modifier also clears the current account on behalf of which the operation is performed as it shouldn't
/// be relied upon when the checks are in progress.
modifier nonReentrantChecksAcquireLock() {
EC contextCache = executionContext;
if (contextCache.areChecksInProgress()) {
revert EVC_ChecksReentrancy();
}
executionContext = contextCache.setChecksInProgress().setOnBehalfOfAccount(address(0));
_;
executionContext = contextCache;
}
///////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS //
///////////////////////////////////////////////////////////////////////////////////////////////
// Execution internals
/// @inheritdoc IEVC
function getRawExecutionContext() external view returns (uint256 context) {
context = EC.unwrap(executionContext);
}
/// @inheritdoc IEVC
function getCurrentOnBehalfOfAccount(address controllerToCheck)
external
view
returns (address onBehalfOfAccount, bool controllerEnabled)
{
onBehalfOfAccount = executionContext.getOnBehalfOfAccount();
// for safety, revert if no account has been authenticated
if (onBehalfOfAccount == address(0)) {
revert EVC_OnBehalfOfAccountNotAuthenticated();
}
controllerEnabled =
controllerToCheck == address(0) ? false : accountControllers[onBehalfOfAccount].contains(controllerToCheck);
}
/// @inheritdoc IEVC
function areChecksDeferred() external view returns (bool) {
return executionContext.areChecksDeferred();
}
/// @inheritdoc IEVC
function areChecksInProgress() external view returns (bool) {
return executionContext.areChecksInProgress();
}
/// @inheritdoc IEVC
function isControlCollateralInProgress() external view returns (bool) {
return executionContext.isControlCollateralInProgress();
}
/// @inheritdoc IEVC
function isOperatorAuthenticated() external view returns (bool) {
return executionContext.isOperatorAuthenticated();
}
/// @inheritdoc IEVC
function isSimulationInProgress() external view returns (bool) {
return executionContext.isSimulationInProgress();
}
// Owners and operators
/// @inheritdoc IEVC
function haveCommonOwner(address account, address otherAccount) external pure returns (bool) {
return haveCommonOwnerInternal(account, otherAccount);
}
/// @inheritdoc IEVC
function getAddressPrefix(address account) external pure returns (bytes19) {
return getAddressPrefixInternal(account);
}
/// @inheritdoc IEVC
function getAccountOwner(address account) external view returns (address) {
return getAccountOwnerInternal(account);
}
/// @inheritdoc IEVC
function isLockdownMode(bytes19 addressPrefix) external view returns (bool) {
return ownerLookup[addressPrefix].isLockdownMode;
}
/// @inheritdoc IEVC
function isPermitDisabledMode(bytes19 addressPrefix) external view returns (bool) {
return ownerLookup[addressPrefix].isPermitDisabledMode;
}
/// @inheritdoc IEVC
function getNonce(bytes19 addressPrefix, uint256 nonceNamespace) external view returns (uint256) {
return nonceLookup[addressPrefix][nonceNamespace];
}
/// @inheritdoc IEVC
function getOperator(bytes19 addressPrefix, address operator) external view returns (uint256) {
return operatorLookup[addressPrefix][operator];
}
/// @inheritdoc IEVC
function isAccountOperatorAuthorized(address account, address operator) external view returns (bool) {
return isAccountOperatorAuthorizedInternal(account, operator);
}
/// @inheritdoc IEVC
function setLockdownMode(bytes19 addressPrefix, bool enabled) public payable virtual onlyOwner(addressPrefix) {
if (ownerLookup[addressPrefix].isLockdownMode != enabled) {
// to increase user security, it is prohibited to disable this mode within the self-call of the permit
// function or within a checks-deferrable call. to disable this mode, the setLockdownMode function must be
// called directly
if (!enabled && (executionContext.areChecksDeferred() || inPermitSelfCall())) {
revert EVC_NotAuthorized();
}
ownerLookup[addressPrefix].isLockdownMode = enabled;
emit LockdownModeStatus(addressPrefix, enabled);
}
}
/// @inheritdoc IEVC
function setPermitDisabledMode(
bytes19 addressPrefix,
bool enabled
) public payable virtual onlyOwner(addressPrefix) {
if (ownerLookup[addressPrefix].isPermitDisabledMode != enabled) {
// to increase user security, it is prohibited to disable this mode within the self-call of the permit
// function (verified in the permit function) or within a checks-deferrable call. to disable this mode the
// setPermitDisabledMode function must be called directly
if (!enabled && executionContext.areChecksDeferred()) {
revert EVC_NotAuthorized();
}
ownerLookup[addressPrefix].isPermitDisabledMode = enabled;
emit PermitDisabledModeStatus(addressPrefix, enabled);
}
}
/// @inheritdoc IEVC
function setNonce(
bytes19 addressPrefix,
uint256 nonceNamespace,
uint256 nonce
) public payable virtual onlyOwner(addressPrefix) {
uint256 currentNonce = nonceLookup[addressPrefix][nonceNamespace];
if (currentNonce >= nonce) {
revert EVC_InvalidNonce();
}
nonceLookup[addressPrefix][nonceNamespace] = nonce;
emit NonceStatus(addressPrefix, nonceNamespace, currentNonce, nonce);
}
/// @inheritdoc IEVC
/// @dev Uses authenticateCaller() function instead of onlyOwner() modifier to authenticate and get the caller
/// address at once.
function setOperator(bytes19 addressPrefix, address operator, uint256 operatorBitField) public payable virtual {
address msgSender =
authenticateCaller({addressPrefix: addressPrefix, allowOperator: false, checkLockdownMode: false});
// the operator can neither be the EVC nor can be one of 256 accounts of the owner
if (operator == address(this) || haveCommonOwnerInternal(msgSender, operator)) {
revert EVC_InvalidAddress();
}
if (operatorLookup[addressPrefix][operator] == operatorBitField) {
revert EVC_InvalidOperatorStatus();
} else {
operatorLookup[addressPrefix][operator] = operatorBitField;
emit OperatorStatus(addressPrefix, operator, operatorBitField);
}
}
/// @inheritdoc IEVC
/// @dev Uses authenticateCaller() function instead of onlyOwnerOrOperator() modifier to authenticate and get the
/// caller address at once.
function setAccountOperator(address account, address operator, bool authorized) public payable virtual {
address msgSender = authenticateCaller({account: account, allowOperator: true, checkLockdownMode: false});
// if the account and the caller have a common owner, the caller must be the owner. if the account and the
// caller don't have a common owner, the caller must be an operator and the owner address is taken from the
// storage. the caller authentication above guarantees that the account owner is already registered hence
// non-zero
address owner = haveCommonOwnerInternal(account, msgSender) ? msgSender : getAccountOwnerInternal(account);
// if it's an operator calling, it can only act for itself and must not be able to change other operators status
if (owner != msgSender && operator != msgSender) {
revert EVC_NotAuthorized();
}
// the operator can neither be the EVC nor can be one of 256 accounts of the owner
if (operator == address(this) || haveCommonOwnerInternal(owner, operator)) {
revert EVC_InvalidAddress();
}
bytes19 addressPrefix = getAddressPrefixInternal(account);
// The bitMask defines which accounts the operator is authorized for. The bitMask is created from the account
// number which is a number up to 2^8 in binary, or 256. 1 << (uint160(owner) ^ uint160(account)) transforms
// that number in an 256-position binary array like 0...010...0, marking the account positionally in a uint256.
uint256 bitMask = 1 << (uint160(owner) ^ uint160(account));
// The operatorBitField is a 256-position binary array, where each 1 signals by position the account that the
// operator is authorized for.
uint256 oldOperatorBitField = operatorLookup[addressPrefix][operator];
uint256 newOperatorBitField = authorized ? oldOperatorBitField | bitMask : oldOperatorBitField & ~bitMask;
if (oldOperatorBitField == newOperatorBitField) {
revert EVC_InvalidOperatorStatus();
} else {
operatorLookup[addressPrefix][operator] = newOperatorBitField;
emit OperatorStatus(addressPrefix, operator, newOperatorBitField);
}
}
// Collaterals management
/// @inheritdoc IEVC
function getCollaterals(address account) external view returns (address[] memory) {
return accountCollaterals[account].get();
}
/// @inheritdoc IEVC
function isCollateralEnabled(address account, address vault) external view returns (bool) {
return accountCollaterals[account].contains(vault);
}
/// @inheritdoc IEVC
function enableCollateral(
address account,
address vault
) public payable virtual nonReentrantChecksAndControlCollateral onlyOwnerOrOperator(account) {
if (vault == address(this)) revert EVC_InvalidAddress();
if (accountCollaterals[account].insert(vault)) {
emit CollateralStatus(account, vault, true);
}
requireAccountStatusCheck(account);
}
/// @inheritdoc IEVC
function disableCollateral(
address account,
address vault
) public payable virtual nonReentrantChecksAndControlCollateral onlyOwnerOrOperator(account) {
if (accountCollaterals[account].remove(vault)) {
emit CollateralStatus(account, vault, false);
}
requireAccountStatusCheck(account);
}
/// @inheritdoc IEVC
function reorderCollaterals(
address account,
uint8 index1,
uint8 index2
) public payable virtual nonReentrantChecksAndControlCollateral onlyOwnerOrOperator(account) {
accountCollaterals[account].reorder(index1, index2);
requireAccountStatusCheck(account);
}
// Controllers management
/// @inheritdoc IEVC
function getControllers(address account) external view returns (address[] memory) {
return accountControllers[account].get();
}
/// @inheritdoc IEVC
function isControllerEnabled(address account, address vault) external view returns (bool) {
return accountControllers[account].contains(vault);
}
/// @inheritdoc IEVC
function enableController(
address account,
address vault
) public payable virtual nonReentrantChecksAndControlCollateral onlyOwnerOrOperator(account) {
if (vault == address(this)) revert EVC_InvalidAddress();
if (accountControllers[account].insert(vault)) {
emit ControllerStatus(account, vault, true);
}
requireAccountStatusCheck(account);
}
/// @inheritdoc IEVC
function disableController(address account) public payable virtual nonReentrantChecksAndControlCollateral {
if (accountControllers[account].remove(msg.sender)) {
emit ControllerStatus(account, msg.sender, false);
}
requireAccountStatusCheck(account);
}
// Permit
/// @inheritdoc IEVC
function permit(
address signer,
address sender,
uint256 nonceNamespace,
uint256 nonce,
uint256 deadline,
uint256 value,
bytes calldata data,
bytes calldata signature
) public payable virtual nonReentrantChecksAndControlCollateral {
// cannot be called within the self-call of the permit function; can occur for nested calls.
// the permit function can be called only by the specified sender
if (inPermitSelfCall() || (sender != address(0) && sender != msg.sender)) {
revert EVC_NotAuthorized();
}
if (signer == address(0) || !isSignerValid(signer)) {
revert EVC_InvalidAddress();
}
bytes19 addressPrefix = getAddressPrefixInternal(signer);
if (ownerLookup[addressPrefix].isPermitDisabledMode) {
revert EVC_PermitDisabledMode();
}
{
uint256 currentNonce = nonceLookup[addressPrefix][nonceNamespace];
if (currentNonce == type(uint256).max || currentNonce != nonce) {
revert EVC_InvalidNonce();
}
}
if (deadline < block.timestamp) {
revert EVC_InvalidTimestamp();
}
if (data.length == 0) {
revert EVC_InvalidData();
}
bytes32 permitHash = getPermitHash(signer, sender, nonceNamespace, nonce, deadline, value, data);
if (
signer != recoverECDSASigner(permitHash, signature)
&& !isValidERC1271Signature(signer, permitHash, signature)
) {
revert EVC_NotAuthorized();
}
unchecked {
nonceLookup[addressPrefix][nonceNamespace] = nonce + 1;
}
emit NonceUsed(addressPrefix, nonceNamespace, nonce);
// EVC address becomes the msg.sender for the duration this self-call, no authentication is required here.
// the signer will be later on authenticated as per data, depending on the functions that will be called
(bool success, bytes memory result) = callWithContextInternal(address(this), signer, value, data);
if (!success) revertBytes(result);
}
// Calls forwarding
/// @inheritdoc IEVC
function call(
address targetContract,
address onBehalfOfAccount,
uint256 value,
bytes calldata data
) public payable virtual nonReentrantChecksAndControlCollateral returns (bytes memory result) {
EC contextCache = executionContext;
executionContext = contextCache.setChecksDeferred();
bool success;
(success, result) = callWithAuthenticationInternal(targetContract, onBehalfOfAccount, value, data);
if (!success) revertBytes(result);
restoreExecutionContext(contextCache);
}
/// @inheritdoc IEVC
function controlCollateral(
address targetCollateral,
address onBehalfOfAccount,
uint256 value,
bytes calldata data
)
public
payable
virtual
nonReentrantChecksAndControlCollateral
onlyController(onBehalfOfAccount)
returns (bytes memory result)
{
if (!accountCollaterals[onBehalfOfAccount].contains(targetCollateral)) {
revert EVC_NotAuthorized();
}
EC contextCache = executionContext;
executionContext = contextCache.setChecksDeferred().setControlCollateralInProgress();
bool success;
(success, result) = callWithContextInternal(targetCollateral, onBehalfOfAccount, value, data);
if (!success) revertBytes(result);
restoreExecutionContext(contextCache);
}
/// @inheritdoc IEVC
function batch(BatchItem[] calldata items) public payable virtual nonReentrantChecksAndControlCollateral {
EC contextCache = executionContext;
executionContext = contextCache.setChecksDeferred();
uint256 length = items.length;
for (uint256 i; i < length; ++i) {
BatchItem calldata item = items[i];
(bool success, bytes memory result) =
callWithAuthenticationInternal(item.targetContract, item.onBehalfOfAccount, item.value, item.data);
if (!success) revertBytes(result);
}
restoreExecutionContext(contextCache);
}
// Simulations
/// @inheritdoc IEVC
function batchRevert(BatchItem[] calldata items) public payable virtual nonReentrantChecksAndControlCollateral {
BatchItemResult[] memory batchItemsResult;
StatusCheckResult[] memory accountsStatusCheckResult;
StatusCheckResult[] memory vaultsStatusCheckResult;
EC contextCache = executionContext;
if (contextCache.areChecksDeferred()) {
revert EVC_SimulationBatchNested();
}
executionContext = contextCache.setChecksDeferred().setSimulationInProgress();
uint256 length = items.length;
batchItemsResult = new BatchItemResult[](length);
for (uint256 i; i < length; ++i) {
BatchItem calldata item = items[i];
(batchItemsResult[i].success, batchItemsResult[i].result) =
callWithAuthenticationInternal(item.targetContract, item.onBehalfOfAccount, item.value, item.data);
}
executionContext = contextCache.setChecksInProgress().setOnBehalfOfAccount(address(0));
accountsStatusCheckResult = checkStatusAllWithResult(SetType.Account);
vaultsStatusCheckResult = checkStatusAllWithResult(SetType.Vault);
executionContext = contextCache;
revert EVC_RevertedBatchResult(batchItemsResult, accountsStatusCheckResult, vaultsStatusCheckResult);
}
/// @inheritdoc IEVC
function batchSimulation(BatchItem[] calldata items)
external
payable
virtual
returns (
BatchItemResult[] memory batchItemsResult,
StatusCheckResult[] memory accountsStatusCheckResult,
StatusCheckResult[] memory vaultsStatusCheckResult
)
{
(bool success, bytes memory result) = address(this).delegatecall(abi.encodeCall(this.batchRevert, items));
if (success) {
revert EVC_BatchPanic();
} else if (result.length < 4 || bytes4(result) != EVC_RevertedBatchResult.selector) {
revertBytes(result);
}
assembly {
let length := mload(result)
// skip 4-byte EVC_RevertedBatchResult selector
result := add(result, 4)
// write new array length = original length - 4-byte selector
// cannot underflow as we require result.length >= 4 above
mstore(result, sub(length, 4))
}
(batchItemsResult, accountsStatusCheckResult, vaultsStatusCheckResult) =
abi.decode(result, (BatchItemResult[], StatusCheckResult[], StatusCheckResult[]));
}
// Account Status Check
/// @inheritdoc IEVC
function getLastAccountStatusCheckTimestamp(address account) external view nonReentrantChecks returns (uint256) {
return accountControllers[account].getMetadata();
}
/// @inheritdoc IEVC
function isAccountStatusCheckDeferred(address account) external view nonReentrantChecks returns (bool) {
return accountStatusChecks.contains(account);
}
/// @inheritdoc IEVC
function requireAccountStatusCheck(address account) public payable virtual {
if (executionContext.areChecksDeferred()) {
accountStatusChecks.insert(account);
} else {
requireAccountStatusCheckInternalNonReentrantChecks(account);
}
}
/// @inheritdoc IEVC
function forgiveAccountStatusCheck(address account)
public
payable
virtual
nonReentrantChecksAcquireLock
onlyController(account)
{
accountStatusChecks.remove(account);
}
// Vault Status Check
/// @inheritdoc IEVC
function isVaultStatusCheckDeferred(address vault) external view nonReentrantChecks returns (bool) {
return vaultStatusChecks.contains(vault);
}
/// @inheritdoc IEVC
function requireVaultStatusCheck() public payable virtual {
if (executionContext.areChecksDeferred()) {
vaultStatusChecks.insert(msg.sender);
} else {
requireVaultStatusCheckInternalNonReentrantChecks(msg.sender);
}
}
/// @inheritdoc IEVC
function forgiveVaultStatusCheck() public payable virtual nonReentrantChecksAcquireLock {
vaultStatusChecks.remove(msg.sender);
}
/// @inheritdoc IEVC
function requireAccountAndVaultStatusCheck(address account) public payable virtual {
if (executionContext.areChecksDeferred()) {
accountStatusChecks.insert(account);
vaultStatusChecks.insert(msg.sender);
} else {
requireAccountStatusCheckInternalNonReentrantChecks(account);
requireVaultStatusCheckInternalNonReentrantChecks(msg.sender);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// INTERNAL FUNCTIONS //
///////////////////////////////////////////////////////////////////////////////////////////////
/// @notice Authenticates the caller of a function.
/// @dev This function checks if the caller is the owner or an authorized operator of the account, and if the
/// account is not in lockdown mode.
/// @param account The account address to authenticate the caller against.
/// @param allowOperator A boolean indicating if operators are allowed to authenticate as the caller.
/// @param checkLockdownMode A boolean indicating if the function should check for lockdown mode on the account.
/// @return The address of the authenticated caller.
function authenticateCaller(
address account,
bool allowOperator,
bool checkLockdownMode
) internal virtual returns (address) {
bytes19 addressPrefix = getAddressPrefixInternal(account);
address owner = ownerLookup[addressPrefix].owner;
bool lockdownMode = ownerLookup[addressPrefix].isLockdownMode;
address msgSender = _msgSender();
bool authenticated = false;
// check if the caller is the owner of the account
if (haveCommonOwnerInternal(account, msgSender)) {
// if the owner is not registered, register it
if (owner == address(0)) {
ownerLookup[addressPrefix].owner = msgSender;
emit OwnerRegistered(addressPrefix, msgSender);
authenticated = true;
} else if (owner == msgSender) {
authenticated = true;
}
}
// if the caller is not the owner, check if it is an operator if operators are allowed
if (!authenticated && allowOperator && isAccountOperatorAuthorizedInternal(account, msgSender)) {
authenticated = true;
}
// must revert if neither the owner nor the operator were authenticated
if (!authenticated) {
revert EVC_NotAuthorized();
}
// revert if the account is in lockdown mode unless the lockdown mode is not being checked
if (checkLockdownMode && lockdownMode) {
revert EVC_LockdownMode();
}
return msgSender;
}
/// @notice Authenticates the caller of a function.
/// @dev This function converts a bytes19 address prefix into a phantom account address which is an account address
/// that belongs to the owner of the address prefix.
/// @param addressPrefix The bytes19 address prefix to authenticate the caller against.
/// @param allowOperator A boolean indicating if operators are allowed to authenticate as the caller.
/// @param checkLockdownMode A boolean indicating if the function should check for lockdown mode on the account.
/// @return The address of the authenticated caller.
function authenticateCaller(
bytes19 addressPrefix,
bool allowOperator,
bool checkLockdownMode
) internal virtual returns (address) {
address phantomAccount = address(uint160(uint152(addressPrefix)) << ACCOUNT_ID_OFFSET);
return authenticateCaller({
account: phantomAccount,
allowOperator: allowOperator,
checkLockdownMode: checkLockdownMode
});
}
/// @notice Internal function to make a call to a target contract with a specific context.
/// @dev This function sets the execution context for the duration of the call.
/// @param targetContract The contract address to call.
/// @param onBehalfOfAccount The account address on behalf of which the call is made.
/// @param value The amount of value to send with the call.
/// @param data The calldata to send with the call.
function callWithContextInternal(
address targetContract,
address onBehalfOfAccount,
uint256 value,
bytes calldata data
) internal virtual returns (bool success, bytes memory result) {
if (value == type(uint256).max) {
value = address(this).balance;
} else if (value > address(this).balance) {
revert EVC_InvalidValue();
}
EC contextCache = executionContext;
address msgSender = _msgSender();
// set the onBehalfOfAccount in the execution context for the duration of the external call.
// considering that the operatorAuthenticated is only meant to be observable by external
// contracts, it is sufficient to set it here rather than in the authentication function.
// apart from the usual scenario (when an owner operates on behalf of its account),
// the operatorAuthenticated should be cleared when about to execute the permit self-call, when
// target contract is equal to the msg.sender in call() and batch(), or when the controlCollateral is in
// progress (in which case the operatorAuthenticated is not relevant)
if (
haveCommonOwnerInternal(onBehalfOfAccount, msgSender) || targetContract == msg.sender
|| targetContract == address(this) || contextCache.isControlCollateralInProgress()
) {
executionContext = contextCache.setOnBehalfOfAccount(onBehalfOfAccount).clearOperatorAuthenticated();
} else {
executionContext = contextCache.setOnBehalfOfAccount(onBehalfOfAccount).setOperatorAuthenticated();
}
emit CallWithContext(
msgSender, getAddressPrefixInternal(onBehalfOfAccount), onBehalfOfAccount, targetContract, bytes4(data)
);
(success, result) = targetContract.call{value: value}(data);
executionContext = contextCache;
}
/// @notice Internal function to call a target contract with necessary authentication.
/// @dev This function decides whether to use delegatecall or a regular call based on the target contract.
/// If the target contract is this contract, it uses delegatecall to preserve msg.sender for authentication.
/// Otherwise, it authenticates the caller if needed and proceeds with a regular call.
/// @param targetContract The contract address to call.
/// @param onBehalfOfAccount The account address on behalf of which the call is made.
/// @param value The amount of value to send with the call.
/// @param data The calldata to send with the call.
/// @return success A boolean indicating if the call was successful.
/// @return result The bytes returned from the call.
function callWithAuthenticationInternal(
address targetContract,
address onBehalfOfAccount,
uint256 value,
bytes calldata data
) internal virtual returns (bool success, bytes memory result) {
if (targetContract == address(this)) {
if (onBehalfOfAccount != address(0)) {
revert EVC_InvalidAddress();
}
if (value != 0) {
revert EVC_InvalidValue();
}
// delegatecall is used here to preserve msg.sender in order to be able to perform authentication
(success, result) = address(this).delegatecall(data);
} else {
// when the target contract is equal to the msg.sender, both in call() and batch(), authentication is not
// required
if (targetContract != msg.sender) {
authenticateCaller({account: onBehalfOfAccount, allowOperator: true, checkLockdownMode: true});
}
(success, result) = callWithContextInternal(targetContract, onBehalfOfAccount, value, data);
}
}
/// @notice Restores the execution context from a cached state.
/// @dev This function restores the execution context to a previously cached state, performing necessary status
/// checks if they are no longer deferred. If checks are no longer deferred, it sets the execution context to
/// indicate checks are in progress and clears the 'on behalf of' account. It then performs status checks for both
/// accounts and vaults before restoring the execution context to the cached state.
/// @param contextCache The cached execution context to restore from.
function restoreExecutionContext(EC contextCache) internal virtual {
if (!contextCache.areChecksDeferred()) {
executionContext = contextCache.setChecksInProgress().setOnBehalfOfAccount(address(0));
checkStatusAll(SetType.Account);
checkStatusAll(SetType.Vault);
}
executionContext = contextCache;
}
/// @notice Checks the status of an account internally.
/// @dev This function first checks the number of controllers for the account. If there are no controllers enabled,
/// it returns true immediately, indicating the account status is valid without further checks. If there is more
/// than one controller, it reverts with an EVC_ControllerViolation error. For a single controller, it proceeds to
/// call the controller to check the account status.
/// @param account The account address to check the status for.
/// @return isValid A boolean indicating if the account status is valid.
/// @return result The bytes returned from the controller call, indicating the account status.
function checkAccountStatusInternal(address account) internal virtual returns (bool isValid, bytes memory result) {
SetStorage storage accountControllersStorage = accountControllers[account];
uint256 numOfControllers = accountControllersStorage.numElements;
address controller = accountControllersStorage.firstElement;
uint8 stamp = accountControllersStorage.stamp;
if (numOfControllers == 0) return (true, "");
else if (numOfControllers > 1) return (false, abi.encodeWithSelector(EVC_ControllerViolation.selector));
bool success;
(success, result) =
controller.call(abi.encodeCall(IVault.checkAccountStatus, (account, accountCollaterals[account].get())));
isValid = success && result.length == 32
&& abi.decode(result, (bytes32)) == bytes32(IVault.checkAccountStatus.selector);
if (isValid) {
accountControllersStorage.numElements = uint8(numOfControllers);
accountControllersStorage.firstElement = controller;
accountControllersStorage.metadata = uint80(block.timestamp);
accountControllersStorage.stamp = stamp;
}
emit AccountStatusCheck(account, controller);
}
function requireAccountStatusCheckInternal(address account) internal virtual {
(bool isValid, bytes memory result) = checkAccountStatusInternal(account);
if (!isValid) {
revertBytes(result);
}
}
function requireAccountStatusCheckInternalNonReentrantChecks(address account)
internal
virtual
nonReentrantChecksAcquireLock
{
requireAccountStatusCheckInternal(account);
}
/// @notice Checks the status of a vault internally.
/// @dev This function makes an external call to the vault to check its status.
/// @param vault The address of the vault to check the status for.
/// @return isValid A boolean indicating if the vault status is valid.
/// @return result The bytes returned from the vault call, indicating the vault status.
function checkVaultStatusInternal(address vault) internal returns (bool isValid, bytes memory result) {
bool success;
(success, result) = vault.call(abi.encodeCall(IVault.checkVaultStatus, ()));
isValid =
success && result.length == 32 && abi.decode(result, (bytes32)) == bytes32(IVault.checkVaultStatus.selector);
emit VaultStatusCheck(vault);
}
function requireVaultStatusCheckInternal(address vault) internal virtual {
(bool isValid, bytes memory result) = checkVaultStatusInternal(vault);
if (!isValid) {
revertBytes(result);
}
}
function requireVaultStatusCheckInternalNonReentrantChecks(address vault)
internal
virtual
nonReentrantChecksAcquireLock
{
requireVaultStatusCheckInternal(vault);