-
Notifications
You must be signed in to change notification settings - Fork 27
/
SecurityCouncilMemberSyncAction.sol
136 lines (119 loc) · 5.87 KB
/
SecurityCouncilMemberSyncAction.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
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.16;
import "./interfaces/IGnosisSafe.sol";
import "./SecurityCouncilMgmtUtils.sol";
import "../gov-action-contracts/execution-record/ActionExecutionRecord.sol";
/// @notice Action contract for updating security council members. Used by the security council management system.
/// Expected to be delegate called into by an Upgrade Executor
contract SecurityCouncilMemberSyncAction is ActionExecutionRecord {
error PreviousOwnerNotFound(address targetOwner, address securityCouncil);
error ExecFromModuleError(bytes data, address securityCouncil);
event UpdateNonceTooLow(
address indexed securityCouncil, uint256 currrentNonce, uint256 providedNonce
);
/// @dev Used in the gnosis safe as the first entry in their ownership linked list
address public constant SENTINEL_OWNERS = address(0x1);
constructor(KeyValueStore _store)
ActionExecutionRecord(_store, "SecurityCouncilMemberSyncAction")
{}
/// @notice Updates members of security council multisig to match provided array
/// @dev This function contains O(n^2) operations, so doesnt scale for large numbers of members. Expected count is 12, which is acceptable.
/// Gnosis OwnerManager handles reverting if address(0) is passed to remove/add owner
/// @param _securityCouncil The security council to update
/// @param _updatedMembers The new list of members. The Security Council will be updated to have this exact list of members
/// @return res indicates whether an update took place
function perform(address _securityCouncil, address[] memory _updatedMembers, uint256 _nonce)
external
returns (bool res)
{
// make sure that _nonce is greater than the last nonce
// we do this to ensure that a previous update does not occur after a later one
// the mechanism just checks greater, not n+1, because the Security Council Manager always
// sends the latest full list of members so it doesn't matter if some updates are missed
// Additionally a retryable ticket could be used to execute the update, and since tickets
// expire if not executed after some time, then allowing updates to be skipped means that the
// system will not be blocked if a retryable ticket is expires
uint256 updateNonce = getUpdateNonce(_securityCouncil);
if (_nonce <= updateNonce) {
// when nonce is too now, we simply return, we don't revert.
// this way an out of date update will actual execute, rather than remaining in an unexecuted state forever
emit UpdateNonceTooLow(_securityCouncil, updateNonce, _nonce);
return false;
}
// store the nonce as a record of execution
// use security council as the key to ensure that updates to different security councils are kept separate
_setUpdateNonce(_securityCouncil, _nonce);
IGnosisSafe securityCouncil = IGnosisSafe(_securityCouncil);
// preserve current threshold, the safe ensures that the threshold is never lower than the member count
uint256 threshold = securityCouncil.getThreshold();
address[] memory previousOwners = securityCouncil.getOwners();
for (uint256 i = 0; i < _updatedMembers.length; i++) {
address member = _updatedMembers[i];
if (!securityCouncil.isOwner(member)) {
_addMember(securityCouncil, member, threshold);
}
}
for (uint256 i = 0; i < previousOwners.length; i++) {
address owner = previousOwners[i];
if (!SecurityCouncilMgmtUtils.isInArray(owner, _updatedMembers)) {
_removeMember(securityCouncil, owner, threshold);
}
}
return true;
}
function _addMember(IGnosisSafe securityCouncil, address _member, uint256 _threshold)
internal
{
_execFromModule(
securityCouncil,
abi.encodeWithSelector(IGnosisSafe.addOwnerWithThreshold.selector, _member, _threshold)
);
}
function _removeMember(IGnosisSafe securityCouncil, address _member, uint256 _threshold)
internal
{
address previousOwner = getPrevOwner(securityCouncil, _member);
_execFromModule(
securityCouncil,
abi.encodeWithSelector(
IGnosisSafe.removeOwner.selector, previousOwner, _member, _threshold
)
);
}
function getPrevOwner(IGnosisSafe securityCouncil, address _owner)
public
view
returns (address)
{
// owners are stored as a linked list and removal requires the previous owner
address[] memory owners = securityCouncil.getOwners();
address previousOwner = SENTINEL_OWNERS;
for (uint256 i = 0; i < owners.length; i++) {
address currentOwner = owners[i];
if (currentOwner == _owner) {
return previousOwner;
}
previousOwner = currentOwner;
}
revert PreviousOwnerNotFound({
targetOwner: _owner,
securityCouncil: address(securityCouncil)
});
}
function getUpdateNonce(address securityCouncil) public view returns (uint256) {
return _get(uint160(securityCouncil));
}
function _setUpdateNonce(address securityCouncil, uint256 nonce) internal {
_set(uint160(securityCouncil), nonce);
}
/// @notice Execute provided operation via gnosis safe's trusted execTransactionFromModule entry point
function _execFromModule(IGnosisSafe securityCouncil, bytes memory data) internal {
if (
!securityCouncil.execTransactionFromModule(
address(securityCouncil), 0, data, OpEnum.Operation.Call
)
) {
revert ExecFromModuleError({data: data, securityCouncil: address(securityCouncil)});
}
}
}