-
Notifications
You must be signed in to change notification settings - Fork 15
/
ConduitController.sol
503 lines (438 loc) · 18.6 KB
/
ConduitController.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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.7;
// prettier-ignore
import {
ConduitControllerInterface
} from "../interfaces/ConduitControllerInterface.sol";
import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
import { Conduit } from "./Conduit.sol";
/**
* @title ConduitController
* @author 0age
* @notice ConduitController enables deploying and managing new conduits, or
* contracts that allow registered callers (or open "channels") to
* transfer approved ERC20/721/1155 tokens on their behalf.
*/
contract ConduitController is ConduitControllerInterface {
// Register keys, owners, new potential owners, and channels by conduit.
mapping(address => ConduitProperties) internal _conduits;
// Set conduit creation code and runtime code hashes as immutable arguments.
bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH;
bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH;
/**
* @dev Initialize contract by deploying a conduit and setting the creation
* code and runtime code hashes as immutable arguments.
*/
constructor() {
// Derive the conduit creation code hash and set it as an immutable.
_CONDUIT_CREATION_CODE_HASH = keccak256(type(Conduit).creationCode);
// Deploy a conduit with the zero hash as the salt.
Conduit zeroConduit = new Conduit{ salt: bytes32(0) }();
// Retrieve the conduit runtime code hash and set it as an immutable.
_CONDUIT_RUNTIME_CODE_HASH = address(zeroConduit).codehash;
}
/**
* @notice Deploy a new conduit using a supplied conduit key and assigning
* an initial owner for the deployed conduit. Note that the first
* twenty bytes of the supplied conduit key must match the caller
* and that a new conduit cannot be created if one has already been
* deployed using the same conduit key.
*
* @param conduitKey The conduit key used to deploy the conduit. Note that
* the first twenty bytes of the conduit key must match
* the caller of this contract.
* @param initialOwner The initial owner to set for the new conduit.
*
* @return conduit The address of the newly deployed conduit.
*/
function createConduit(bytes32 conduitKey, address initialOwner)
external
override
returns (address conduit)
{
// If the first 20 bytes of the conduit key do not match the caller...
if (address(uint160(bytes20(conduitKey))) != msg.sender) {
// Revert with an error indicating that the creator is invalid.
revert InvalidCreator();
}
// Derive address from deployer, conduit key and creation code hash.
conduit = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
conduitKey,
_CONDUIT_CREATION_CODE_HASH
)
)
)
)
);
// If derived conduit exists, as evidenced by comparing runtime code...
if (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH) {
// Revert with an error indicating that the conduit already exists.
revert ConduitAlreadyExists(conduit);
}
// Deploy the conduit via CREATE2 using the conduit key as the salt.
new Conduit{ salt: conduitKey }();
// Set the supplied initial owner as the owner of the conduit.
_conduits[conduit].owner = initialOwner;
// Set conduit key used to deploy the conduit to enable reverse lookup.
_conduits[conduit].key = conduitKey;
// Emit an event indicating that the conduit has been deployed.
emit NewConduit(conduit, conduitKey);
// Emit an event indicating that conduit ownership has been assigned.
emit OwnershipTransferred(conduit, address(0), initialOwner);
}
/**
* @notice Open or close a channel on a given conduit, thereby allowing the
* specified account to execute transfers against that conduit.
* Extreme care must be taken when updating channels, as malicious
* or vulnerable channels can transfer any ERC20, ERC721 and ERC1155
* tokens where the token holder has granted the conduit approval.
* Only the owner of the conduit in question may call this function.
*
* @param conduit The conduit for which to open or close the channel.
* @param channel The channel to open or close on the conduit.
* @param isOpen A boolean indicating whether to open or close the channel.
*/
function updateChannel(
address conduit,
address channel,
bool isOpen
) external override {
// Ensure the caller is the current owner of the conduit in question.
_assertCallerIsConduitOwner(conduit);
// Call the conduit, updating the channel.
ConduitInterface(conduit).updateChannel(channel, isOpen);
// Retrieve storage region where channels for the conduit are tracked.
ConduitProperties storage conduitProperties = _conduits[conduit];
// Retrieve the index, if one currently exists, for the updated channel.
uint256 channelIndexPlusOne = (
conduitProperties.channelIndexesPlusOne[channel]
);
// Determine whether the updated channel is already tracked as open.
bool channelPreviouslyOpen = channelIndexPlusOne != 0;
// If the channel has been set to open and was previously closed...
if (isOpen && !channelPreviouslyOpen) {
// Add the channel to the channels array for the conduit.
conduitProperties.channels.push(channel);
// Add new open channel length to associated mapping as index + 1.
conduitProperties.channelIndexesPlusOne[channel] = (
conduitProperties.channels.length
);
} else if (!isOpen && channelPreviouslyOpen) {
// Set a previously open channel as closed via "swap & pop" method.
// Decrement located index to get the index of the closed channel.
uint256 removedChannelIndex = channelIndexPlusOne - 1;
// Use length of channels array to determine index of last channel.
uint256 finalChannelIndex = conduitProperties.channels.length - 1;
// If closed channel is not last channel in the channels array...
if (finalChannelIndex != removedChannelIndex) {
// Retrieve the final channel and place the value on the stack.
address finalChannel = (
conduitProperties.channels[finalChannelIndex]
);
// Overwrite the removed channel using the final channel value.
conduitProperties.channels[removedChannelIndex] = finalChannel;
// Update final index in associated mapping to removed index.
conduitProperties.channelIndexesPlusOne[finalChannel] = (
channelIndexPlusOne
);
}
// Remove the last channel from the channels array for the conduit.
conduitProperties.channels.pop();
// Remove the closed channel from associated mapping of indexes.
delete conduitProperties.channelIndexesPlusOne[channel];
}
}
/**
* @notice Initiate conduit ownership transfer by assigning a new potential
* owner for the given conduit. Once set, the new potential owner
* may call `acceptOwnership` to claim ownership of the conduit.
* Only the owner of the conduit in question may call this function.
*
* @param conduit The conduit for which to initiate ownership transfer.
*/
function transferOwnership(address conduit, address newPotentialOwner)
external
override
{
// Ensure the caller is the current owner of the conduit in question.
_assertCallerIsConduitOwner(conduit);
// Ensure the new potential owner is not an invalid address.
if (newPotentialOwner == address(0)) {
revert NewPotentialOwnerIsZeroAddress(conduit);
}
// Emit an event indicating that the potential owner has been updated.
emit PotentialOwnerUpdated(conduit, newPotentialOwner);
// Set the new potential owner as the potential owner of the conduit.
_conduits[conduit].potentialOwner = newPotentialOwner;
}
/**
* @notice Clear the currently set potential owner, if any, from a conduit.
* Only the owner of the conduit in question may call this function.
*
* @param conduit The conduit for which to cancel ownership transfer.
*/
function cancelOwnershipTransfer(address conduit) external override {
// Ensure the caller is the current owner of the conduit in question.
_assertCallerIsConduitOwner(conduit);
// Emit an event indicating that the potential owner has been cleared.
emit PotentialOwnerUpdated(conduit, address(0));
// Clear the current new potential owner from the conduit.
delete _conduits[conduit].potentialOwner;
}
/**
* @notice Accept ownership of a supplied conduit. Only accounts that the
* current owner has set as the new potential owner may call this
* function.
*
* @param conduit The conduit for which to accept ownership.
*/
function acceptOwnership(address conduit) external override {
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// If caller does not match current potential owner of the conduit...
if (msg.sender != _conduits[conduit].potentialOwner) {
// Revert, indicating that caller is not current potential owner.
revert CallerIsNotNewPotentialOwner(conduit);
}
// Emit an event indicating that the potential owner has been cleared.
emit PotentialOwnerUpdated(conduit, address(0));
// Clear the current new potential owner from the conduit.
delete _conduits[conduit].potentialOwner;
// Emit an event indicating conduit ownership has been transferred.
emit OwnershipTransferred(
conduit,
_conduits[conduit].owner,
msg.sender
);
// Set the caller as the owner of the conduit.
_conduits[conduit].owner = msg.sender;
}
/**
* @notice Retrieve the current owner of a deployed conduit.
*
* @param conduit The conduit for which to retrieve the associated owner.
*
* @return owner The owner of the supplied conduit.
*/
function ownerOf(address conduit)
external
view
override
returns (address owner)
{
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// Retrieve the current owner of the conduit in question.
owner = _conduits[conduit].owner;
}
/**
* @notice Retrieve the conduit key for a deployed conduit via reverse
* lookup.
*
* @param conduit The conduit for which to retrieve the associated conduit
* key.
*
* @return conduitKey The conduit key used to deploy the supplied conduit.
*/
function getKey(address conduit)
external
view
override
returns (bytes32 conduitKey)
{
// Attempt to retrieve a conduit key for the conduit in question.
conduitKey = _conduits[conduit].key;
// Revert if no conduit key was located.
if (conduitKey == bytes32(0)) {
revert NoConduit();
}
}
/**
* @notice Derive the conduit associated with a given conduit key and
* determine whether that conduit exists (i.e. whether it has been
* deployed).
*
* @param conduitKey The conduit key used to derive the conduit.
*
* @return conduit The derived address of the conduit.
* @return exists A boolean indicating whether the derived conduit has been
* deployed or not.
*/
function getConduit(bytes32 conduitKey)
external
view
override
returns (address conduit, bool exists)
{
// Derive address from deployer, conduit key and creation code hash.
conduit = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
conduitKey,
_CONDUIT_CREATION_CODE_HASH
)
)
)
)
);
// Determine whether conduit exists by retrieving its runtime code.
exists = (conduit.codehash == _CONDUIT_RUNTIME_CODE_HASH);
}
/**
* @notice Retrieve the potential owner, if any, for a given conduit. The
* current owner may set a new potential owner via
* `transferOwnership` and that owner may then accept ownership of
* the conduit in question via `acceptOwnership`.
*
* @param conduit The conduit for which to retrieve the potential owner.
*
* @return potentialOwner The potential owner, if any, for the conduit.
*/
function getPotentialOwner(address conduit)
external
view
override
returns (address potentialOwner)
{
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// Retrieve the current potential owner of the conduit in question.
potentialOwner = _conduits[conduit].potentialOwner;
}
/**
* @notice Retrieve the status (either open or closed) of a given channel on
* a conduit.
*
* @param conduit The conduit for which to retrieve the channel status.
* @param channel The channel for which to retrieve the status.
*
* @return isOpen The status of the channel on the given conduit.
*/
function getChannelStatus(address conduit, address channel)
external
view
override
returns (bool isOpen)
{
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// Retrieve the current channel status for the conduit in question.
isOpen = _conduits[conduit].channelIndexesPlusOne[channel] != 0;
}
/**
* @notice Retrieve the total number of open channels for a given conduit.
*
* @param conduit The conduit for which to retrieve the total channel count.
*
* @return totalChannels The total number of open channels for the conduit.
*/
function getTotalChannels(address conduit)
external
view
override
returns (uint256 totalChannels)
{
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// Retrieve the total open channel count for the conduit in question.
totalChannels = _conduits[conduit].channels.length;
}
/**
* @notice Retrieve an open channel at a specific index for a given conduit.
* Note that the index of a channel can change as a result of other
* channels being closed on the conduit.
*
* @param conduit The conduit for which to retrieve the open channel.
* @param channelIndex The index of the channel in question.
*
* @return channel The open channel, if any, at the specified channel index.
*/
function getChannel(address conduit, uint256 channelIndex)
external
view
override
returns (address channel)
{
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// Retrieve the total open channel count for the conduit in question.
uint256 totalChannels = _conduits[conduit].channels.length;
// Ensure that the supplied index is within range.
if (channelIndex >= totalChannels) {
revert ChannelOutOfRange(conduit);
}
// Retrieve the channel at the given index.
channel = _conduits[conduit].channels[channelIndex];
}
/**
* @notice Retrieve all open channels for a given conduit. Note that calling
* this function for a conduit with many channels will revert with
* an out-of-gas error.
*
* @param conduit The conduit for which to retrieve open channels.
*
* @return channels An array of open channels on the given conduit.
*/
function getChannels(address conduit)
external
view
override
returns (address[] memory channels)
{
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// Retrieve all of the open channels on the conduit in question.
channels = _conduits[conduit].channels;
}
/**
* @dev Retrieve the conduit creation code and runtime code hashes.
*/
function getConduitCodeHashes()
external
view
override
returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash)
{
// Retrieve the conduit creation code hash from runtime.
creationCodeHash = _CONDUIT_CREATION_CODE_HASH;
// Retrieve the conduit runtime code hash from runtime.
runtimeCodeHash = _CONDUIT_RUNTIME_CODE_HASH;
}
/**
* @dev Internal view function to revert if the caller is not the owner of a
* given conduit.
*
* @param conduit The conduit for which to assert ownership.
*/
function _assertCallerIsConduitOwner(address conduit) internal view {
// Ensure that the conduit in question exists.
_assertConduitExists(conduit);
// If the caller does not match the current owner of the conduit...
if (msg.sender != _conduits[conduit].owner) {
// Revert, indicating that the caller is not the owner.
revert CallerIsNotOwner(conduit);
}
}
/**
* @dev Internal view function to revert if a given conduit does not exist.
*
* @param conduit The conduit for which to assert existence.
*/
function _assertConduitExists(address conduit) internal view {
// Attempt to retrieve a conduit key for the conduit in question.
if (_conduits[conduit].key == bytes32(0)) {
// Revert if no conduit key was located.
revert NoConduit();
}
}
}