-
Notifications
You must be signed in to change notification settings - Fork 9
/
Safe.sol
174 lines (143 loc) · 5.75 KB
/
Safe.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
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.15;
import { ISafe } from "./interfaces/ISafe.sol";
contract Safe is ISafe {
uint256 public constant OWNER_COUNT = 3;
uint256 public constant VETO_DURATION = 1 minutes;
address[OWNER_COUNT] public owners;
mapping(bytes32 => uint256) internal queueHashToTimestamp;
mapping(bytes32 => bool) internal transactionExecuted;
constructor(address[OWNER_COUNT] memory _owners) {
_setupOwners(_owners);
}
// ======================================= MODIFIERS ======================================
modifier onlySelf() {
if (msg.sender != address(this)) revert NotAuthorized();
_;
}
modifier onlyOwner() {
if (!isOwner(msg.sender)) revert NotAuthorized();
_;
}
// ======================================= PERMISSIONED FUNCTIONS ======================================
/**
* @notice Replaces a current owner with a new owner.
*
* @param ownerIndex The index of the owner to replace.
* @param newOwner The new owner address.
*/
function replaceOwner(uint256 ownerIndex, address newOwner) external onlySelf {
if (ownerIndex >= OWNER_COUNT) revert InvalidIndex();
if (newOwner == address(0)) revert OwnerCannotBeZeroAddress();
for (uint256 i = 0; i < OWNER_COUNT; i++) {
if (owners[i] == newOwner) {
revert DuplicateOwner();
}
}
owners[ownerIndex] = newOwner;
}
/**
* @notice Allow owners to veto a queued transaction.
*
* @param queueHash The hash of the queued transaction.
*/
function vetoTransaction(bytes32 queueHash) external onlyOwner {
uint256 queueTimestamp = queueHashToTimestamp[queueHash];
if (queueTimestamp == 0) revert TransactionNotQueued();
if (block.timestamp >= queueTimestamp + VETO_DURATION) revert NotInVetoPeriod();
delete queueHashToTimestamp[queueHash];
}
// ========================================= MUTATIVE FUNCTIONS ========================================
/**
* @notice Allow users to queue transactions.
*
* @param v The v value of signatures from all owners.
* @param r The r value of signatures from all owners.
* @param s The s value of signatures from all owners.
* @param transaction The transaction to execute.
* @return queueHash The hash of the queued transaction.
*/
function queueTransaction(
uint8[OWNER_COUNT] calldata v,
bytes32[OWNER_COUNT] calldata r,
bytes32[OWNER_COUNT] calldata s,
Transaction calldata transaction
) external returns (bytes32 queueHash) {
if (!isOwner(transaction.signer)) revert SignerIsNotOwner();
queueHash = keccak256(abi.encode(
transaction,
v,
r,
s
));
queueHashToTimestamp[queueHash] = block.timestamp;
}
/**
* @notice Execute a queued transaction.
*
* @param v The v value of signatures from all owners.
* @param r The r value of signatures from all owners.
* @param s The s value of signatures from all owners.
* @param transaction The transaction to execute.
* @param signatureIndex The index of the signature to use.
* @return success Whether the executed transaction succeeded.
* @return returndata Return data from the executed transaction.
*/
function executeTransaction(
uint8[OWNER_COUNT] calldata v,
bytes32[OWNER_COUNT] calldata r,
bytes32[OWNER_COUNT] calldata s,
Transaction calldata transaction,
uint256 signatureIndex
) external payable returns (bool success, bytes memory returndata) {
if (signatureIndex >= OWNER_COUNT) revert InvalidIndex();
bytes32 queueHash = keccak256(abi.encode(
transaction,
v,
r,
s
));
uint256 queueTimestamp = queueHashToTimestamp[queueHash];
if (queueTimestamp == 0) revert TransactionNotQueued();
if (block.timestamp < queueTimestamp + VETO_DURATION) revert StillInVetoPeriod();
bytes32 txHash = keccak256(abi.encode(transaction));
if (transactionExecuted[txHash]) revert TransactionAlreadyExecuted();
address signer = ecrecover(
txHash,
v[signatureIndex],
r[signatureIndex],
s[signatureIndex]
);
if (signer != transaction.signer) revert InvalidSignature();
transactionExecuted[txHash] = true;
(success, returndata) = transaction.to.call{ value: transaction.value }(transaction.data);
}
// ========================================= VIEW FUNCTIONS ========================================
/**
* @notice Check is an address is an owner.
*
* @param owner The address to check for.
* @return Whether the address is an owner.
*/
function isOwner(address owner) public view returns (bool) {
for (uint256 i = 0; i < OWNER_COUNT; i++) {
if (owner == owners[i]) {
return true;
}
}
return false;
}
// ========================================= HELPERS ========================================
/**
* @notice Initialize owners for the safe.
*
* @param newOwners The addresses of all owners.
*/
function _setupOwners(address[OWNER_COUNT] memory newOwners) internal {
for (uint256 i = 0; i < OWNER_COUNT; i++) {
if (newOwners[i] == address(0)) revert OwnerCannotBeZeroAddress();
owners[i] = newOwners[i];
}
}
receive() external payable {}
}