-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathRegistry.sol
366 lines (280 loc) · 16.7 KB
/
Registry.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
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.9;
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { IERC20Token } from "./interfaces/IERC20Token.sol";
import { IHermesContract } from "./interfaces/IHermesContract.sol";
import { FundsRecovery } from "./FundsRecovery.sol";
import { Utils } from "./Utils.sol";
interface Channel {
function initialize(address _token, address _dex, address _identityHash, address _hermesId, uint256 _fee) external;
}
contract Registry is FundsRecovery, Utils {
using ECDSA for bytes32;
uint256 public lastNonce;
address payable public dex; // Any uniswap v2 compatible DEX router address
uint256 public minimalHermesStake;
Registry public parentRegistry; // Contract could have parent registry if Registry SC was already upgraded
struct Implementation {
address channelImplAddress;
address hermesImplAddress;
}
Implementation[] internal implementations;
struct Hermes {
address operator; // hermes operator who will sign promises
uint256 implVer; // version of hermes implementation smart contract
function() external view returns(uint256) stake;
bytes url; // hermes service URL
}
mapping(address => Hermes) private hermeses;
mapping(address => address) private identities; // key: identity, value: beneficiary wallet address
event RegisteredIdentity(address indexed identity, address beneficiary);
event RegisteredHermes(address indexed hermesId, address hermesOperator, bytes ur);
event HermesURLUpdated(address indexed hermesId, bytes newURL);
event ConsumerChannelCreated(address indexed identity, address indexed hermesId, address channelAddress);
event BeneficiaryChanged(address indexed identity, address newBeneficiary);
event MinimalHermesStakeChanged(uint256 newMinimalStake);
// Reject any ethers sent to this smart-contract
receive() external payable {
revert("Registry: Rejecting tx with ethers sent");
}
// We're using `initialize` instead of `constructor` to ensure easy way to deploy Registry into
// deterministic address on any EVM compatible chain. Registry should be first be deployed using
// `deployRegistry` scripts and then initialized with wanted token and implementations.
function initialize(address _tokenAddress, address payable _dexAddress, uint256 _minimalHermesStake, address _channelImplementation, address _hermesImplementation, address payable _parentRegistry) public onlyOwner {
require(!isInitialized(), "Registry: is already initialized");
minimalHermesStake = _minimalHermesStake;
require(_tokenAddress != address(0), "Registry: token smart contract can't be deployed into 0x0 address");
token = IERC20Token(_tokenAddress);
require(_dexAddress != address(0), "Registry: dex can't be deployed into 0x0 address");
dex = _dexAddress;
// Set initial channel implementations
setImplementations(_channelImplementation, _hermesImplementation);
// We set initial owner to be sure
transferOwnership(msg.sender);
// Set parent registry, if `0x0` then this is root registry
parentRegistry = Registry(_parentRegistry);
}
function isInitialized() public view returns (bool) {
return address(token) != address(0);
}
// Register provider and open his channel with given hermes
// _stakeAmount - it's amount of tokens staked into hermes to guarantee incomming channel's balance.
// _beneficiary - payout address during settlements in hermes channel, if provided 0x0 then will be set to consumer channel address.
function registerIdentity(address _hermesId, uint256 _stakeAmount, uint256 _transactorFee, address _beneficiary, bytes memory _signature) public {
require(isActiveHermes(_hermesId), "Registry: provided hermes have to be active");
// Check if given signature is valid
address _identity = keccak256(abi.encodePacked(getChainID(), address(this), _hermesId, _stakeAmount, _transactorFee, _beneficiary)).recover(_signature);
require(_identity != address(0), "Registry: wrong identity signature");
// Tokens amount to get from channel to cover tx fee and provider's stake
uint256 _totalFee = _stakeAmount + _transactorFee;
require(_totalFee <= token.balanceOf(getChannelAddress(_identity, _hermesId)), "Registry: not enough funds in channel to cover fees");
// Open consumer channel
_openChannel(_identity, _hermesId, _beneficiary, _totalFee);
// If stake is provided we additionally are opening channel with hermes (a.k.a provider channel)
if (_stakeAmount > 0) {
IHermesContract(_hermesId).openChannel(_identity, _stakeAmount);
}
// Pay fee for transaction maker
if (_transactorFee > 0) {
token.transfer(msg.sender, _transactorFee);
}
}
// Deploys consumer channel and sets beneficiary as newly created channel address
function openConsumerChannel(address _hermesId, uint256 _transactorFee, bytes memory _signature) public {
require(isActiveHermes(_hermesId), "Registry: provided hermes have to be active");
// Check if given signature is valid
address _identity = keccak256(abi.encodePacked(getChainID(), address(this), _hermesId, _transactorFee)).recover(_signature);
require(_identity != address(0), "Registry: wrong channel openinig signature");
require(_transactorFee <= token.balanceOf(getChannelAddress(_identity, _hermesId)), "Registry: not enough funds in channel to cover fees");
_openChannel(_identity, _hermesId, address(0), _transactorFee);
}
// Allows to securely deploy channel's smart contract without consumer signature
function openConsumerChannel(address _identity, address _hermesId) public {
require(isActiveHermes(_hermesId), "Registry: provided hermes have to be active");
require(!isChannelOpened(_identity, _hermesId), "Registry: such consumer channel is already opened");
_openChannel(_identity, _hermesId, address(0), 0);
}
// Deploy payment channel for given consumer identity
// We're using minimal proxy (EIP1167) to save on gas cost and blockchain space.
function _openChannel(address _identity, address _hermesId, address _beneficiary, uint256 _fee) internal returns (address) {
bytes32 _salt = keccak256(abi.encodePacked(_identity, _hermesId));
bytes memory _code = getProxyCode(getChannelImplementation(hermeses[_hermesId].implVer));
Channel _channel = Channel(deployMiniProxy(uint256(_salt), _code));
_channel.initialize(address(token), dex, _identity, _hermesId, _fee);
emit ConsumerChannelCreated(_identity, _hermesId, address(_channel));
// If beneficiary was not provided, then we're going to use consumer channel for that
if (_beneficiary == address(0)) {
_beneficiary = address(_channel);
}
// Mark identity as registered (only during first channel opening)
if (!isRegistered(_identity)) {
identities[_identity] = _beneficiary;
emit RegisteredIdentity(_identity, _beneficiary);
}
return address(_channel);
}
function registerHermes(address _hermesOperator, uint256 _hermesStake, uint16 _hermesFee, uint256 _minChannelStake, uint256 _maxChannelStake, bytes memory _url) public {
require(isInitialized(), "Registry: only initialized registry can register hermeses");
require(_hermesOperator != address(0), "Registry: hermes operator can't be zero address");
require(_hermesStake >= minimalHermesStake, "Registry: hermes have to stake at least minimal stake amount");
address _hermesId = getHermesAddress(_hermesOperator);
require(!isHermes(_hermesId), "Registry: hermes already registered");
// Deploy hermes contract (mini proxy which is pointing to implementation)
IHermesContract _hermes = IHermesContract(deployMiniProxy(uint256(uint160(_hermesOperator)), getProxyCode(getHermesImplementation())));
// Transfer stake into hermes smart contract
token.transferFrom(msg.sender, address(_hermes), _hermesStake);
// Initialise hermes
_hermes.initialize(address(token), _hermesOperator, _hermesFee, _minChannelStake, _maxChannelStake, dex);
// Save info about newly created hermes
hermeses[_hermesId] = Hermes(_hermesOperator, getLastImplVer(), _hermes.getStake, _url);
// Approve hermes contract to `transferFrom` registry (used during hermes channel openings)
token.approve(_hermesId, type(uint256).max);
emit RegisteredHermes(_hermesId, _hermesOperator, _url);
}
function getChannelAddress(address _identity, address _hermesId) public view returns (address) {
bytes32 _code = keccak256(getProxyCode(getChannelImplementation(hermeses[_hermesId].implVer)));
bytes32 _salt = keccak256(abi.encodePacked(_identity, _hermesId));
return getCreate2Address(_salt, _code);
}
function getHermes(address _hermesId) public view returns (Hermes memory) {
return isHermes(_hermesId) || !hasParentRegistry() ? hermeses[_hermesId] : parentRegistry.getHermes(_hermesId);
}
function getHermesAddress(address _hermesOperator) public view returns (address) {
bytes32 _code = keccak256(getProxyCode(getHermesImplementation()));
return getCreate2Address(bytes32(uint256(uint160(_hermesOperator))), _code);
}
function getHermesAddress(address _hermesOperator, uint256 _implVer) public view returns (address) {
bytes32 _code = keccak256(getProxyCode(getHermesImplementation(_implVer)));
return getCreate2Address(bytes32(uint256(uint160(_hermesOperator))), _code);
}
function getHermesURL(address _hermesId) public view returns (bytes memory) {
return hermeses[_hermesId].url;
}
function updateHermesURL(address _hermesId, bytes memory _url, bytes memory _signature) public {
require(isActiveHermes(_hermesId), "Registry: provided hermes has to be active");
// Check if given signature is valid
address _operator = keccak256(abi.encodePacked(address(this), _hermesId, _url, lastNonce++)).recover(_signature);
require(_operator == hermeses[_hermesId].operator, "wrong signature");
// Update URL
hermeses[_hermesId].url = _url;
emit HermesURLUpdated(_hermesId, _url);
}
// ------------ UTILS ------------
function getCreate2Address(bytes32 _salt, bytes32 _code) internal view returns (address) {
return address(uint160(uint256(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
bytes32(_salt),
bytes32(_code)
)))));
}
function getProxyCode(address _implementation) public pure returns (bytes memory) {
// `_code` is EIP 1167 - Minimal Proxy Contract
// more information: https://eips.ethereum.org/EIPS/eip-1167
bytes memory _code = hex"3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3";
bytes20 _targetBytes = bytes20(_implementation);
for (uint8 i = 0; i < 20; i++) {
_code[20 + i] = _targetBytes[i];
}
return _code;
}
function deployMiniProxy(uint256 _salt, bytes memory _code) internal returns (address payable) {
address payable _addr;
assembly {
_addr := create2(0, add(_code, 0x20), mload(_code), _salt)
if iszero(extcodesize(_addr)) {
revert(0, 0)
}
}
return _addr;
}
function getBeneficiary(address _identity) public view returns (address) {
if (hasParentRegistry())
return parentRegistry.getBeneficiary(_identity);
return identities[_identity];
}
function setBeneficiary(address _identity, address _newBeneficiary, bytes memory _signature) public {
require(_newBeneficiary != address(0), "Registry: beneficiary can't be zero address");
// Always set beneficiary into root registry
if (hasParentRegistry()) {
parentRegistry.setBeneficiary(_identity, _newBeneficiary, _signature);
} else {
lastNonce = lastNonce + 1;
// In signatures we should always use root registry (for backward compatibility)
address _rootRegistry = hasParentRegistry() ? address(parentRegistry) : address(this);
address _signer = keccak256(abi.encodePacked(getChainID(), _rootRegistry, _identity, _newBeneficiary, lastNonce)).recover(_signature);
require(_signer == _identity, "Registry: have to be signed by identity owner");
identities[_identity] = _newBeneficiary;
emit BeneficiaryChanged(_identity, _newBeneficiary);
}
}
function setMinimalHermesStake(uint256 _newMinimalStake) public onlyOwner {
require(isInitialized(), "Registry: only initialized registry can set new minimal hermes stake");
minimalHermesStake = _newMinimalStake;
emit MinimalHermesStakeChanged(_newMinimalStake);
}
// -------- UTILS TO WORK WITH CHANNEL AND HERMES IMPLEMENTATIONS ---------
function getChannelImplementation() public view returns (address) {
return implementations[getLastImplVer()].channelImplAddress;
}
function getChannelImplementation(uint256 _implVer) public view returns (address) {
return implementations[_implVer].channelImplAddress;
}
function getHermesImplementation() public view returns (address) {
return implementations[getLastImplVer()].hermesImplAddress;
}
function getHermesImplementation(uint256 _implVer) public view returns (address) {
return implementations[_implVer].hermesImplAddress;
}
function setImplementations(address _newChannelImplAddress, address _newHermesImplAddress) public onlyOwner {
require(isInitialized(), "Registry: only initialized registry can set new implementations");
require(isSmartContract(_newChannelImplAddress) && isSmartContract(_newHermesImplAddress), "Registry: implementations have to be smart contracts");
implementations.push(Implementation(_newChannelImplAddress, _newHermesImplAddress));
}
// Version of latest hermes and channel implementations
function getLastImplVer() public view returns (uint256) {
return implementations.length-1;
}
// ------------------------------------------------------------------------
function isSmartContract(address _addr) internal view returns (bool) {
uint _codeLength;
assembly {
_codeLength := extcodesize(_addr)
}
return _codeLength != 0;
}
// If `parentRegistry` is not set, this is root registry and should return false
function hasParentRegistry() public view returns (bool) {
return address(parentRegistry) != address(0);
}
function isRegistered(address _identity) public view returns (bool) {
if (hasParentRegistry())
return parentRegistry.isRegistered(_identity);
// If we know its beneficiary address it is registered identity
return identities[_identity] != address(0);
}
function isHermes(address _hermesId) public view returns (bool) {
// To check if it actually properly created hermes address, we need to check if he has operator
// and if with that operator we'll get proper hermes address which has code deployed there.
address _hermesOperator = hermeses[_hermesId].operator;
uint256 _implVer = hermeses[_hermesId].implVer;
address _addr = getHermesAddress(_hermesOperator, _implVer);
if (_addr != _hermesId)
return false; // hermesId should be same as generated address
return isSmartContract(_addr) || parentRegistry.isHermes(_hermesId);
}
function isActiveHermes(address _hermesId) internal view returns (bool) {
// First we have to ensure that given address is registered hermes and only then check its status
require(isHermes(_hermesId), "Registry: hermes have to be registered");
IHermesContract.Status status = IHermesContract(_hermesId).getStatus();
return status == IHermesContract.Status.Active;
}
function isChannelOpened(address _identity, address _hermesId) public view returns (bool) {
return isSmartContract(getChannelAddress(_identity, _hermesId)) || isSmartContract(parentRegistry.getChannelAddress(_identity, _hermesId));
}
function transferCollectedFeeTo(address _beneficiary) public onlyOwner{
uint256 _collectedFee = token.balanceOf(address(this));
require(_collectedFee > 0, "collected fee cannot be less than zero");
token.transfer(_beneficiary, _collectedFee);
}
}