/
SphereXEngine.sol
214 lines (185 loc) · 7.88 KB
/
SphereXEngine.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
// SPDX-License-Identifier: UNLICENSED
// (c) SphereX 2023 Terms&Conditions
pragma solidity >=0.6.0;
import "./Ownable.sol";
import "./ISphereXEngine.sol";
/**
* @title SphereX Engine
* @notice Gathers information about an ongoing transaction and reverts if it seems malicious
*/
contract SphereXEngine is Ownable, ISphereXEngine {
bytes8 private _engineRules; // By default the contract will be deployed with no guarding rules activated
mapping(address => bool) private _allowedSenders;
mapping(uint256 => bool) private _allowedPatterns;
// We initialize the next variables to 1 and not 0 to save gas costs on future transactions
uint256 private _currentPattern = PATTERN_START;
uint256 private _callDepth = DEPTH_START;
// Represent keccak256(abi.encode(block.number, tx.origin))
bytes32 private _currentBlockOriginHash = bytes32(uint256(1));
uint256 private constant PATTERN_START = 1;
uint256 private constant DEPTH_START = 1;
bytes32 private constant DEACTIVATED = bytes32(0);
modifier returnsIfNotActivated() {
if (_engineRules == DEACTIVATED) {
return;
}
_;
}
modifier onlyApprovedSenders() {
require(_allowedSenders[msg.sender], "!SX:SENDERS");
_;
}
// ============ Management ============
/**
* Activate the guardian rules
* @param rules bytes8 representing the new rules to activate.
*/
function activateRules(bytes8 rules) external onlyOwner {
_engineRules = rules;
}
/**
* Deactivates the engine, the calls will return without being checked
*/
function deactivateRules() external onlyOwner {
_engineRules = bytes8(uint64(0));
}
/**
* Adds addresses that will be served by this engine. An address that was never added will get a revert if it tries to call the engine.
* @param senders list of address to add to the set of allowed addresses
*/
function addAllowedSender(address[] calldata senders) external onlyOwner {
for (uint256 i = 0; i < senders.length; ++i) {
_allowedSenders[senders[i]] = true;
}
}
/**
* Removes address so that they will not get served when calling the engine. Transaction from these addresses will get reverted.
* @param senders list of address to stop service.
*/
function removeAllowedSender(address[] calldata senders) external onlyOwner {
for (uint256 i = 0; i < senders.length; ++i) {
_allowedSenders[senders[i]] = false;
}
}
/**
* Add allowed patterns - these are representation of allowed flows of transactions, and prefixes of these flows
* @param patterns list of flows to allow as valid and non-malicious flows
*/
function addAllowedPatterns(uint256[] calldata patterns) external onlyOwner {
for (uint256 i = 0; i < patterns.length; ++i) {
_allowedPatterns[patterns[i]] = true;
}
}
/**
* Remove allowed patterns - these are representation flows of transactions, and prefixes of these flows,
* that are no longer considered valid and benign
* @param patterns list of flows that no longer considered valid and non-malicious
*/
function removeAllowedPatterns(uint256[] calldata patterns) external onlyOwner {
for (uint256 i = 0; i < patterns.length; ++i) {
_allowedPatterns[patterns[i]] = false;
}
}
// ============ CF ============
/**
* Checks if rule1 is activated.
*/
function _isRule1Activated() private view returns (bool) {
return (_engineRules & bytes8(uint64(1))) > 0;
}
/**
* Checks if rule2 us activated.
*/
function _isRule2Activated() private view returns (bool) {
return (_engineRules & bytes8(uint64(2))) > 0;
}
/**
* update the current CF pattern with a new number,
* when exiting a function we check the validity of the pattern.
* @param num element to add to the flow. Poistive number represents start of function, negative exit.
* @param forceCheck force the check of the current pattern, even if normal test conditions don't exist.
*/
function _addCFElement(int16 num, bool forceCheck) private {
// Upon entry to a new function if we are configured to PrefixTxFlow we should check if we are at the same transaction
// or a new one. in case of a new one we need to reinit the _currentPattern, and save
// the new transaction "hash" (block.number+tx.origin)
if (num > 0 && _isRule2Activated()) {
bytes32 currentBlockOriginHash = keccak256(abi.encode(block.number, tx.origin));
if (currentBlockOriginHash != _currentBlockOriginHash) {
_currentPattern = PATTERN_START;
_currentBlockOriginHash = currentBlockOriginHash;
}
}
_currentPattern = uint256(keccak256(abi.encode(num, _currentPattern)));
if (num > 0) {
++_callDepth;
} else if (num < 0) {
--_callDepth;
} else {
revert("!SX:ERROR");
}
if ((_callDepth == DEPTH_START) || (forceCheck)) {
_checkCallFlow();
}
// If we are configured to CF then if we reach depth == DEPTH_START we should reinit the
// _currentPattern
if (_isRule1Activated() && _callDepth == DEPTH_START) {
_currentPattern = PATTERN_START;
}
}
/**
* Check if the current call flow pattern (that is, the result of the rolling hash) is an allowed pattern.
*/
function _checkCallFlow() private view {
require(_allowedPatterns[_currentPattern], "!SX:DETECTED");
}
/**
* This is the function that is actually called by the modifier of the protected contract before the body of the function.
* This is used only for external call functions.
* @param num id of function to add. Should be positive
* @param sender For future use
* @param data For future use
* @return result in the future will return insturction on what storage slots to gather, but not used for now
*/
function sphereXValidatePre(int16 num, address sender, bytes calldata data)
external
override
returnsIfNotActivated // may return empty bytes32[]
onlyApprovedSenders
returns (bytes32[] memory result)
{
_addCFElement(num, false);
return result;
}
/**
* This is the function that is actually called by the modifier of the protected contract after the body of the function.
* This is used only for external call functions (that is, external, and public when called outside the contract).
* @param num id of function to add. Should be negative
* @param valuesBefore For future use
* @param valuesAfter For future use
*/
function sphereXValidatePost(int16 num, uint256 gas, bytes32[] calldata valuesBefore, bytes32[] calldata valuesAfter)
external
override
returnsIfNotActivated
onlyApprovedSenders
{
_addCFElement(num, true);
}
/**
* This is the function that is actually called by the modifier of the protected contract before and after the body of the function.
* This is used only for internal function calls (internal and private functions).
* @param num id of function to add.
*/
function sphereXValidateInternalPre(int16 num) external override returnsIfNotActivated onlyApprovedSenders {
_addCFElement(num, false);
}
/**
* This is the function that is actually called by the modifier of the protected contract before and after the body of the function.
* This is used only for internal function calls (internal and private functions).
* @param num id of function to add.
*/
function sphereXValidateInternalPost(int16 num, uint256 gas) external override returnsIfNotActivated onlyApprovedSenders {
_addCFElement(num, false);
}
}