/
ProxyPerpsV2.sol
261 lines (217 loc) · 9.33 KB
/
ProxyPerpsV2.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
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
// Internal references
import "./Proxyable.sol";
/**
* Based on Proxy.sol that adds routing capabilities to route specific function calls (selectors) to
* specific implementations and flagging the routes if are views in order to not call
* proxyable.setMessageSender() that is mutative (resulting in a revert).
*
* In order to manage the routes it provides two onlyOwner functions (`addRoute` and `removeRoute`), and
* some helper views to get the size of the route list (`getRoutesLength`), the list of routes (`getRoutesPage`),
* and a list of all the targeted contracts.
*/
// https://docs.synthetix.io/contracts/source/contracts/ProxyPerpsV2
contract ProxyPerpsV2 is Owned {
/* ----- Dynamic router storage ----- */
struct Route {
bytes4 selector;
address implementation;
bool isView;
}
// Route definition and index to quickly access it
Route[] internal _routes;
mapping(bytes4 => uint) internal _routeIndexes;
// number of routes referencing a target, if number is greater than zero, it means the address is a valid target
mapping(address => uint) internal _targetReferences;
// list of valid target addresses (more than zero references in the routes)
address[] internal _routedTargets;
constructor(address _owner) public Owned(_owner) {}
/* ----- Dynamic router administration ----- */
function _contains(bytes4 selector) internal view returns (bool) {
if (_routes.length == 0) {
return false;
}
uint index = _routeIndexes[selector];
return index != 0 || _routes[0].selector == selector;
}
function _removeTargetReference(address implementation) internal {
require(_targetReferences[implementation] > 0, "Target not referenced.");
// Decrement the references
_targetReferences[implementation] -= 1;
// if was the latest reference, remove it from the _routedTargets and emit an event
if (_targetReferences[implementation] == 0) {
// Accepting a for loop since implementations for a market is going to be a very limited number (initially only 2)
for (uint idx = 0; idx < _routedTargets.length; idx++) {
if (_routedTargets[idx] == implementation) {
// remove it by bringing the last one to that position and poping the latest item (if it's the latest one will do an unecessary write)
_routedTargets[idx] = _routedTargets[_routedTargets.length - 1];
_routedTargets.pop();
break;
}
}
emit TargetedRouteRemoved(implementation);
}
}
function addRoute(
bytes4 selector,
address implementation,
bool isView
) external onlyOwner {
require(selector != bytes4(0), "Invalid nil selector");
if (_contains(selector)) {
// Update data
Route storage route = _routes[_routeIndexes[selector]];
// Remove old implementation reference
_removeTargetReference(route.implementation);
route.selector = selector;
route.implementation = implementation;
route.isView = isView;
} else {
// Add data
_routeIndexes[selector] = _routes.length;
Route memory newRoute;
newRoute.selector = selector;
newRoute.implementation = implementation;
newRoute.isView = isView;
_routes.push(newRoute);
}
// Add to targeted references
_targetReferences[implementation] += 1;
if (_targetReferences[implementation] == 1) {
// First reference, add to routed targets and emit the event
_routedTargets.push(implementation);
emit TargetedRouteAdded(implementation);
}
emit RouteUpdated(selector, implementation, isView);
}
function removeRoute(bytes4 selector) external onlyOwner {
require(_contains(selector), "Selector not in set.");
// Replace the removed selector with the last selector of the list.
uint index = _routeIndexes[selector];
uint lastIndex = _routes.length - 1; // We required that selector is in the list, so it is not empty.
// Remove target reference
_removeTargetReference(_routes[index].implementation);
// Ensure target is in latest index
if (index != lastIndex) {
// No need to shift the last selector if it is the one we want to delete.
Route storage shiftedElement = _routes[lastIndex];
_routes[index] = shiftedElement;
_routeIndexes[shiftedElement.selector] = index;
}
// Remove target
_routes.pop();
delete _routeIndexes[selector];
emit RouteRemoved(selector);
}
function getRoute(bytes4 selector) external view returns (Route memory) {
if (!_contains(selector)) {
return Route(0, address(0), false);
}
return _routes[_routeIndexes[selector]];
}
function getRoutesLength() external view returns (uint) {
return _routes.length;
}
function getRoutesPage(uint index, uint pageSize) external view returns (Route[] memory) {
// NOTE: This implementation should be converted to slice operators if the compiler is updated to v0.6.0+
uint endIndex = index + pageSize; // The check below that endIndex <= index handles overflow.
// If the page extends past the end of the list, truncate it.
if (endIndex > _routes.length) {
endIndex = _routes.length;
}
if (endIndex <= index) {
return new Route[](0);
}
uint n = endIndex - index; // We already checked for negative overflow.
Route[] memory page = new Route[](n);
for (uint i; i < n; i++) {
page[i] = _routes[i + index];
}
return page;
}
function getAllTargets() external view returns (address[] memory) {
return _routedTargets;
}
///// BASED ON PROXY.SOL /////
/* ----- Proxy based on Proxy.sol ----- */
function _emit(
bytes calldata callData,
uint numTopics,
bytes32 topic1,
bytes32 topic2,
bytes32 topic3,
bytes32 topic4
) external onlyTargets {
uint size = callData.length;
bytes memory _callData = callData;
assembly {
/* The first 32 bytes of callData contain its length (as specified by the abi).
* Length is assumed to be a uint256 and therefore maximum of 32 bytes
* in length. It is also leftpadded to be a multiple of 32 bytes.
* This means moving call_data across 32 bytes guarantees we correctly access
* the data itself. */
switch numTopics
case 0 {
log0(add(_callData, 32), size)
}
case 1 {
log1(add(_callData, 32), size, topic1)
}
case 2 {
log2(add(_callData, 32), size, topic1, topic2)
}
case 3 {
log3(add(_callData, 32), size, topic1, topic2, topic3)
}
case 4 {
log4(add(_callData, 32), size, topic1, topic2, topic3, topic4)
}
}
}
// solhint-disable no-complex-fallback
function() external payable {
bytes4 sig4 = msg.sig;
require(_contains(sig4), "Invalid selector");
// Identify target
address implementation = _routes[_routeIndexes[sig4]].implementation;
bool isView = _routes[_routeIndexes[sig4]].isView;
if (isView) {
assembly {
let free_ptr := mload(0x40)
calldatacopy(free_ptr, 0, calldatasize)
/* We must explicitly forward ether to the underlying contract as well. */
let result := staticcall(gas, implementation, free_ptr, calldatasize, 0, 0)
returndatacopy(free_ptr, 0, returndatasize)
if iszero(result) {
revert(free_ptr, returndatasize)
}
return(free_ptr, returndatasize)
}
} else {
// Mutable call setting Proxyable.messageSender as this is using call not delegatecall
Proxyable(implementation).setMessageSender(msg.sender);
assembly {
let free_ptr := mload(0x40)
calldatacopy(free_ptr, 0, calldatasize)
/* We must explicitly forward ether to the underlying contract as well. */
let result := call(gas, implementation, callvalue, free_ptr, calldatasize, 0, 0)
returndatacopy(free_ptr, 0, returndatasize)
if iszero(result) {
revert(free_ptr, returndatasize)
}
return(free_ptr, returndatasize)
}
}
}
modifier onlyTargets {
require(_targetReferences[msg.sender] > 0, "Must be a proxy target");
_;
}
event RouteUpdated(bytes4 route, address implementation, bool isView);
event RouteRemoved(bytes4 route);
event TargetedRouteAdded(address targetedRoute);
event TargetedRouteRemoved(address targetedRoute);
}