forked from JurgenSchouppe/ThorNode-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ThunderFactory.sol
362 lines (298 loc) · 11.5 KB
/
ThunderFactory.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
// Copyright (c) 2018 The VeChainThor developers
// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>
pragma solidity ^0.4.24;
import "./XAccessControl.sol";
import "./auction/ClockAuction.sol";
import "./utility/interfaces/IVIP181.sol";
contract IEnergy {
function transfer(address _to, uint256 _amount) external;
}
contract ThunderFactory is XAccessControl {
IEnergy constant Energy = IEnergy(uint160(bytes6("Energy")));
// This is the token contract address for the token that is required to be held in order to apply for a node.
IVIP181 public requiredToken;
/// @dev The address of the ClockAuction contract that handles sales of xtoken
ClockAuction public saleAuction;
/// @dev The interval between two transfers
uint64 public transferCooldown = 1 days;
/// @dev A time delay when to start monitor after the token is transfered
uint64 public leadTime = 4 hours;
/// @dev The XToken param struct
struct TokenParameters {
uint256 minBalance;
uint64 ripeDays;
uint64 rewardRatio;
}
enum strengthLevel {
None,
Connect,
Harbor,
Consensus,
Legacy
}
/// @dev Mapping from strength level to token params
mapping(uint8 => TokenParameters) internal strengthParams;
/// @dev The main Token struct. Each token is represented by a copy of this structure.
struct Token {
uint64 createdAt;
uint64 updatedAt;
bool onUpgrade;
strengthLevel level;
uint64 lastTransferTime;
}
/// @dev An array containing the Token struct for all XTokens in existence.
/// The ID of each token is actually an index into this array and starts at 1.
Token[] internal tokens;
/// @dev The counter of normal tokens and xtokens
uint64 public normalTokenCount;
/// @dev Mapping from token ID to owner and its reverse mapping.
/// Every address can only hold one token at most.
mapping(uint256 => address) public idToOwner;
mapping(address => uint256) public ownerToId;
// Mapping from token ID to approved address
mapping (uint256 => address) internal tokenApprovals;
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
event NewUpgradeApply(uint256 indexed _tokenId, address indexed _applier, strengthLevel _level, uint64 _applyTime, uint64 _applyBlockno);
event CancelUpgrade(uint256 indexed _tokenId, address indexed _owner);
event LevelChanged(uint256 indexed _tokenId, address indexed _owner, strengthLevel _fromLevel, strengthLevel _toLevel);
event AuctionCancelled(uint256 indexed _auctionId, uint256 indexed _tokenId);
constructor(address requiredTokenAddress) public {
requiredToken = IVIP181(requiredTokenAddress);
// the index of valid tokens should start from 1
tokens.push(Token(0, 0, false, strengthLevel.None, 0));
strengthParams[1] = TokenParameters(1000000 ether, 30, 0); // Connect
strengthParams[2] = TokenParameters(2500000 ether, 45, 8); // Harbor
strengthParams[3] = TokenParameters(10000000 ether, 60, 32); // Consensus
strengthParams[4] = TokenParameters(30000000 ether, 90, 57); // Legacy
}
/// @dev To tell whether the address is holding a token(x or normal)
function isToken(address _target)
public
view
returns(bool)
{
return tokens[ownerToId[_target]].level > strengthLevel.None;
}
/// @dev Apply for a token or upgrade the holding token.
/// Note that bypassing a level is forbidden, it has to upgrade one by one.
function applyUpgrade(strengthLevel _toLvl)
external
whenNotPaused
{
uint256 _tokenId = ownerToId[msg.sender];
if (_tokenId == 0) {
// a new token
_tokenId = _add(msg.sender, strengthLevel.None, false);
}
Token storage token = tokens[_tokenId];
require(!token.onUpgrade, "still upgrading");
require(!saleAuction.isOnAuction(_tokenId), "cancel auction first");
// Bypass check.
require(
uint8(token.level) + 1 == uint8(_toLvl)
&& _toLvl <= strengthLevel.Legacy,
"invalid _toLvl");
// The balance of msg.sender must meet the requirement of target level's minbalance
require(requiredToken.balanceOf(msg.sender) >= strengthParams[uint8(_toLvl)].minBalance, "insufficient balance");
token.onUpgrade = true;
token.updatedAt = uint64(now);
emit NewUpgradeApply(_tokenId, msg.sender, _toLvl, uint64(block.timestamp), uint64(block.number));
}
/// @dev Cancel the upgrade application.
/// Note that this method can be called by the token holder or admin.
function cancelUpgrade(uint256 _tokenId)
public
{
require(_exist(_tokenId), "token not exist");
Token storage token = tokens[_tokenId];
address _owner = idToOwner[_tokenId];
require(token.onUpgrade, "not on upgrading");
// The token holder or admin allowed.
require(_owner == msg.sender || operators[msg.sender], "permission denied");
if (token.level == strengthLevel.None) {
_destroy(_tokenId);
} else {
token.onUpgrade = false;
token.updatedAt = uint64(now);
}
emit CancelUpgrade(_tokenId, _owner);
}
function getMetadata(uint256 _tokenId)
public
view
returns(address, strengthLevel, bool, bool, uint64, uint64, uint64)
{
if (_exist(_tokenId)) {
Token memory token = tokens[_tokenId];
return (
idToOwner[_tokenId],
token.level,
token.onUpgrade,
saleAuction.isOnAuction(_tokenId),
token.lastTransferTime,
token.createdAt,
token.updatedAt
);
}
}
function getTokenParams(strengthLevel _level)
public
view
returns(uint256, uint64, uint64)
{
TokenParameters memory _params = strengthParams[uint8(_level)];
return (_params.minBalance, _params.ripeDays, _params.rewardRatio);
}
/// @dev To tell whether a token can be transfered.
function canTransfer(uint256 _tokenId)
public
view
returns(bool)
{
return
_exist(_tokenId)
&& !tokens[_tokenId].onUpgrade
&& !blackList[idToOwner[_tokenId]] // token not in black list
&& now > (tokens[_tokenId].lastTransferTime + transferCooldown);
}
/// Admin Methods
function setTransferCooldown(uint64 _cooldown)
external
onlyOperator
{
transferCooldown = _cooldown;
}
function setLeadTime(uint64 _leadtime)
external
onlyOperator
{
leadTime = _leadtime;
}
/// @dev Upgrade a token to the passed level.
function upgradeTo(uint256 _tokenId, strengthLevel _toLvl)
external
onlyOperator
{
require(tokens[_tokenId].level < _toLvl, "invalid level");
require(!saleAuction.isOnAuction(_tokenId), "cancel auction first");
tokens[_tokenId].onUpgrade = false;
_levelChange(_tokenId, _toLvl);
}
/// @dev Downgrade a token to the passed level.
function downgradeTo(uint256 _tokenId, strengthLevel _toLvl)
external
onlyOperator
{
require(tokens[_tokenId].level > _toLvl, "invalid level");
require(now > (tokens[_tokenId].lastTransferTime + leadTime), "cannot downgrade token");
if (saleAuction.isOnAuction(_tokenId)) {
_cancelAuction(_tokenId);
}
if (tokens[_tokenId].onUpgrade) {
cancelUpgrade(_tokenId);
}
_levelChange(_tokenId, _toLvl);
}
/// @dev Adds a new token and stores it. This method should be called
/// when the input data is known to be valid and will generate a Transfer event.
function addToken(address _addr, strengthLevel _lvl, bool _onUpgrade, uint64 _applyUpgradeTime, uint64 _applyUpgradeBlockno)
external
onlyOperator
{
require(!_exist(_addr), "you already hold a token");
// This will assign ownership, and also emit the Transfer event.
uint256 newTokenId = _add(_addr, _lvl, _onUpgrade);
// Update token counter
normalTokenCount++;
// For data imgaration
if (_onUpgrade) {
emit NewUpgradeApply(newTokenId, _addr, _lvl, _applyUpgradeTime, _applyUpgradeBlockno);
}
}
/// @dev Send VTHO bonus to the token's holder
function sendBonusTo(address _to, uint256 _amount)
external
onlyOperator
{
require(_to != address(0), "invalid address");
require(_amount > 0, "invalid amount");
// Transfer VTHO from this contract to _to address, it will throw when fail
Energy.transfer(_to, _amount);
}
/// Internal Methods
function _add(address _owner, strengthLevel _lvl, bool _onUpgrade)
internal
returns(uint256)
{
Token memory _token = Token(uint64(now), uint64(now), _onUpgrade, _lvl, uint64(now));
uint256 _newTokenId = tokens.push(_token) - 1;
ownerToId[_owner] = _newTokenId;
idToOwner[_newTokenId] = _owner;
emit Transfer(0, _owner, _newTokenId);
return _newTokenId;
}
function _destroy(uint256 _tokenId)
internal
{
address _owner = idToOwner[_tokenId];
delete idToOwner[_tokenId];
delete ownerToId[_owner];
delete tokens[_tokenId];
//
emit Transfer(_owner, 0, _tokenId);
}
function _levelChange(uint256 _tokenId, strengthLevel _toLvl)
internal
{
address _owner = idToOwner[_tokenId];
Token storage token = tokens[_tokenId];
strengthLevel _fromLvl = token.level;
if (_toLvl == strengthLevel.None) {
_destroy(_tokenId);
} else {
token.level = _toLvl;
token.updatedAt = uint64(now);
}
// Update token counter
if(strengthLevel.Connect <= _fromLvl && _fromLvl <= strengthLevel.Legacy) {
normalTokenCount--;
}
if(strengthLevel.Connect <= _toLvl && _toLvl <= strengthLevel.Legacy ) {
normalTokenCount++;
}
emit LevelChanged(_tokenId, _owner, _fromLvl, _toLvl);
}
function _exist(uint256 _tokenId)
internal
view
returns(bool)
{
return idToOwner[_tokenId] > address(0);
}
function _exist(address _owner)
internal
view
returns(bool)
{
return ownerToId[_owner] > 0;
}
/// @notice Internal function to clear current approval of a given token ID
/// @param _tokenId uint256 ID of the token to be transferred
function _clearApproval(uint256 _tokenId)
internal
{
delete tokenApprovals[_tokenId];
}
/// @notice Internal function to cancel the ongoing auction
/// @param _tokenId uint256 ID of the token
function _cancelAuction(uint256 _tokenId)
internal
{
_clearApproval(_tokenId);
(uint256 _autionId,,,,,) = saleAuction.getAuction(_tokenId);
emit AuctionCancelled(_autionId, _tokenId);
saleAuction.cancelAuction(_tokenId);
}
}