-
Notifications
You must be signed in to change notification settings - Fork 18
/
DiamondCut.sol
137 lines (107 loc) · 6.51 KB
/
DiamondCut.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../interfaces/IDiamondCut.sol";
import "../libraries/Diamond.sol";
import "../Config.sol";
import "./Base.sol";
/// @title DiamondCut contract responsible for the management of upgrades.
/// @author Matter Labs
contract DiamondCutFacet is Base, IDiamondCut {
constructor() {
// Caution check for config value.
// Should be greater than 0, otherwise zero approvals will be enough to make an instant upgrade!
assert(SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE > 0);
}
/// @dev Starts the upgrade process. Only the current governor can propose an upgrade.
/// @param _facetCuts The set of proposed changes to the facets (adding/replacement/removing)
/// @param _initAddress Address of the fallback contract that will be called after the upgrade execution
function proposeDiamondCut(Diamond.FacetCut[] calldata _facetCuts, address _initAddress) external onlyGovernor {
require(s.diamondCutStorage.proposedDiamondCutTimestamp == 0, "a3"); // proposal already exists
// NOTE: governor commits only to the `facetCuts` and `initAddress`, but not to the calldata on `initAddress` call.
// That means the governor can call `initAddress` with ANY calldata while executing the upgrade.
s.diamondCutStorage.proposedDiamondCutHash = keccak256(abi.encode(_facetCuts, _initAddress));
s.diamondCutStorage.proposedDiamondCutTimestamp = block.timestamp;
s.diamondCutStorage.currentProposalId += 1;
emit DiamondCutProposal(_facetCuts, _initAddress);
}
/// @notice Removes the upgrade proposal. Only current governor can remove proposal.
function cancelDiamondCutProposal() external onlyGovernor {
emit DiamondCutProposalCancelation(
s.diamondCutStorage.currentProposalId,
s.diamondCutStorage.proposedDiamondCutHash
);
require(_resetProposal(), "g1"); // failed cancel diamond cut
}
/// @notice Executes a proposed governor upgrade. Only the current governor can execute the upgrade.
/// NOTE: Governor can execute diamond cut ONLY with proposed `facetCuts` and `initAddress`.
/// `initCalldata` can be arbitrarily.
function executeDiamondCutProposal(Diamond.DiamondCutData calldata _diamondCut) external onlyGovernor {
Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage();
bool approvedBySecurityCouncil = s.diamondCutStorage.securityCouncilEmergencyApprovals >=
SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE;
bool upgradeNoticePeriodPassed = block.timestamp >=
s.diamondCutStorage.proposedDiamondCutTimestamp + UPGRADE_NOTICE_PERIOD;
require(approvedBySecurityCouncil || upgradeNoticePeriodPassed, "a6"); // notice period should expire
require(approvedBySecurityCouncil || !diamondStorage.isFrozen, "f3");
// should not be frozen or should have enough security council approvals
require(
s.diamondCutStorage.proposedDiamondCutHash ==
keccak256(abi.encode(_diamondCut.facetCuts, _diamondCut.initAddress)),
"a4"
); // proposal should be created
require(_resetProposal(), "a5"); // failed reset proposal
if (diamondStorage.isFrozen) {
diamondStorage.isFrozen = false;
emit Unfreeze(s.diamondCutStorage.lastDiamondFreezeTimestamp);
}
Diamond.diamondCut(_diamondCut);
emit DiamondCutProposalExecution(_diamondCut);
}
/// @notice Instantly pause the functionality of all freezable facets & their selectors
function emergencyFreezeDiamond() external onlyGovernor {
Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage();
require(!diamondStorage.isFrozen, "a9"); // diamond proxy is frozen already
_resetProposal();
diamondStorage.isFrozen = true;
// Limited-time freezing feature will be added in the future upgrades, so keeping this variable for simplification
s.diamondCutStorage.lastDiamondFreezeTimestamp = block.timestamp;
emit EmergencyFreeze();
}
/// @notice Unpause the functionality of all freezable facets & their selectors
function unfreezeDiamond() external onlyGovernor {
Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage();
require(diamondStorage.isFrozen, "a7"); // diamond proxy is not frozen
_resetProposal();
diamondStorage.isFrozen = false;
emit Unfreeze(s.diamondCutStorage.lastDiamondFreezeTimestamp);
}
/// @notice Gives another approval for the instant upgrade (diamond cut) by the security council member
/// @param _diamondCutHash The hash of the diamond cut that security council members want to approve. Needed to prevent unintentional approvals, including reorg attacks
function approveEmergencyDiamondCutAsSecurityCouncilMember(bytes32 _diamondCutHash) external {
require(s.diamondCutStorage.securityCouncilMembers[msg.sender], "a9"); // not a security council member
uint256 currentProposalId = s.diamondCutStorage.currentProposalId;
require(s.diamondCutStorage.securityCouncilMemberLastApprovedProposalId[msg.sender] < currentProposalId, "ao"); // already approved this proposal
s.diamondCutStorage.securityCouncilMemberLastApprovedProposalId[msg.sender] = currentProposalId;
require(s.diamondCutStorage.proposedDiamondCutTimestamp != 0, "f0"); // there is no proposed diamond cut
require(s.diamondCutStorage.proposedDiamondCutHash == _diamondCutHash, "f1"); // proposed diamond cut do not match to the approved
uint256 securityCouncilEmergencyApprovals = s.diamondCutStorage.securityCouncilEmergencyApprovals;
s.diamondCutStorage.securityCouncilEmergencyApprovals = securityCouncilEmergencyApprovals + 1;
emit EmergencyDiamondCutApproved(
msg.sender,
currentProposalId,
securityCouncilEmergencyApprovals,
_diamondCutHash
);
}
/// @dev Set up the proposed diamond cut state to the default values
/// @return Whether the proposal is reset or it was already empty
function _resetProposal() private returns (bool) {
if (s.diamondCutStorage.proposedDiamondCutTimestamp == 0) {
return false;
}
delete s.diamondCutStorage.proposedDiamondCutHash;
delete s.diamondCutStorage.proposedDiamondCutTimestamp;
delete s.diamondCutStorage.securityCouncilEmergencyApprovals;
return true;
}
}