-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathTellorPlayground.sol
548 lines (514 loc) · 17.8 KB
/
TellorPlayground.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
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TellorPlayground {
// Events
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
event NewReport(
bytes32 _queryId,
uint256 _time,
bytes _value,
uint256 _reward,
uint256 _nonce,
bytes _queryData,
address _reporter
);
event NewStaker(address _staker, uint256 _amount);
event TipAdded(
address indexed _user,
bytes32 indexed _queryId,
uint256 _tip,
uint256 _totalTip,
bytes _queryData
);
event StakeWithdrawRequested(address _staker, uint256 _amount);
event StakeWithdrawn(address _staker);
event Transfer(address indexed from, address indexed to, uint256 value);
// Storage
mapping(bytes32 => address) public addresses;
mapping(bytes32 => mapping(uint256 => bool)) public isDisputed; //queryId -> timestamp -> value
mapping(bytes32 => mapping(uint256 => address)) public reporterByTimestamp;
mapping(address => StakeInfo) stakerDetails; //mapping from a persons address to their staking info
mapping(bytes32 => uint256[]) public timestamps;
mapping(bytes32 => uint256) public tips; // mapping of data IDs to the amount of TRB they are tipped
mapping(bytes32 => mapping(uint256 => bytes)) public values; //queryId -> timestamp -> value
mapping(bytes32 => uint256[]) public voteRounds; // mapping of vote identifier hashes to an array of dispute IDs
mapping(address => mapping(address => uint256)) private _allowances;
mapping(address => uint256) private _balances;
uint256 public constant timeBasedReward = 5e17; // time based reward for a reporter for successfully submitting a value
uint256 public timeOfLastNewValue = block.timestamp; // time of the last new value, originally set to the block timestamp
uint256 public tipsInContract; // number of tips within the contract
uint256 public voteCount;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
// Structs
struct StakeInfo {
uint256 startDate; //stake start date
uint256 stakedBalance; // staked balance
uint256 lockedBalance; // amount locked for withdrawal
uint256 reporterLastTimestamp; // timestamp of reporter's last reported value
uint256 reportsSubmitted; // total number of reports submitted by reporter
}
// Functions
/**
* @dev Initializes playground parameters
*/
constructor() {
_name = "TellorPlayground";
_symbol = "TRBP";
_decimals = 18;
addresses[
keccak256(abi.encodePacked("_GOVERNANCE_CONTRACT"))
] = address(this);
}
/**
* @dev Approves amount that an address is alowed to spend of behalf of another
* @param _spender The address which is allowed to spend the tokens
* @param _amount The amount that msg.sender is allowing spender to use
* @return bool Whether the transaction succeeded
*
*/
function approve(address _spender, uint256 _amount)
public
virtual
returns (bool)
{
_approve(msg.sender, _spender, _amount);
return true;
}
/**
* @dev A mock function to create a dispute
* @param _queryId The tellorId to be disputed
* @param _timestamp the timestamp of the value to be disputed
*/
function beginDispute(bytes32 _queryId, uint256 _timestamp) external {
values[_queryId][_timestamp] = bytes("");
isDisputed[_queryId][_timestamp] = true;
voteCount++;
voteRounds[keccak256(abi.encodePacked(_queryId, _timestamp))].push(
voteCount
);
}
/**
* @dev Public function to mint tokens to the given address
* @param _user The address which will receive the tokens
*/
function faucet(address _user) external {
_mint(_user, 1000 ether);
}
/**
* @dev A mock function to submit a value to be read without reporter staking needed
* @param _queryId the ID to associate the value to
* @param _value the value for the queryId
* @param _nonce the current value count for the query id
* @param _queryData the data used by reporters to fulfill the data query
*/
// slither-disable-next-line timestamp
function submitValue(
bytes32 _queryId,
bytes calldata _value,
uint256 _nonce,
bytes memory _queryData
) external {
require(
_nonce == timestamps[_queryId].length,
"nonce should be correct"
);
require(
_queryId == keccak256(_queryData) || uint256(_queryId) <= 100,
"id must be hash of bytes data"
);
values[_queryId][block.timestamp] = _value;
timestamps[_queryId].push(block.timestamp);
// Send tips + timeBasedReward to reporter and reset tips for ID
(uint256 _tip, uint256 _reward) = getCurrentReward(_queryId);
if (_reward + _tip > 0) {
transfer(msg.sender, _reward + _tip);
}
timeOfLastNewValue = block.timestamp;
tipsInContract -= _tip;
tips[_queryId] = 0;
reporterByTimestamp[_queryId][block.timestamp] = msg.sender;
stakerDetails[msg.sender].reporterLastTimestamp = block.timestamp;
stakerDetails[msg.sender].reportsSubmitted++;
emit NewReport(
_queryId,
block.timestamp,
_value,
_tip + _reward,
_nonce,
_queryData,
msg.sender
);
}
/**
* @dev Adds a tip to a given query ID.
* @param _queryId is the queryId to look up
* @param _amount is the amount of tips
* @param _queryData is the extra bytes data needed to fulfill the request
*/
function tipQuery(
bytes32 _queryId,
uint256 _amount,
bytes memory _queryData
) external {
require(
_queryId == keccak256(_queryData) || uint256(_queryId) <= 100,
"id must be hash of bytes data"
);
_transfer(msg.sender, address(this), _amount);
_amount = _amount / 2;
_burn(address(this), _amount);
tipsInContract += _amount;
tips[_queryId] += _amount;
emit TipAdded(
msg.sender,
_queryId,
_amount,
tips[_queryId],
_queryData
);
}
/**
* @dev Transfer tokens from one user to another
* @param _recipient The destination address
* @param _amount The amount of tokens, including decimals, to transfer
* @return bool If the transfer succeeded
*/
function transfer(address _recipient, uint256 _amount)
public
virtual
returns (bool)
{
_transfer(msg.sender, _recipient, _amount);
return true;
}
/**
* @dev Transfer tokens from user to another
* @param _sender The address which owns the tokens
* @param _recipient The destination address
* @param _amount The quantity of tokens to transfer
* @return bool Whether the transfer succeeded
*/
function transferFrom(
address _sender,
address _recipient,
uint256 _amount
) public virtual returns (bool) {
_transfer(_sender, _recipient, _amount);
_approve(
_sender,
msg.sender,
_allowances[_sender][msg.sender] - _amount
);
return true;
}
// Tellor Flex
/**
* @dev Allows a reporter to submit stake
* @param _amount amount of tokens to stake
*/
function depositStake(uint256 _amount) external {
StakeInfo storage _staker = stakerDetails[msg.sender];
if (_staker.lockedBalance > 0) {
if (_staker.lockedBalance >= _amount) {
_staker.lockedBalance -= _amount;
} else {
require(
_transferFrom(
msg.sender,
address(this),
_amount - _staker.lockedBalance
)
);
_staker.lockedBalance = 0;
}
} else {
require(_transferFrom(msg.sender, address(this), _amount));
}
_staker.startDate = block.timestamp; // This resets their stake start date to now
_staker.stakedBalance += _amount;
emit NewStaker(msg.sender, _amount);
}
/**
* @dev Allows a reporter to request to withdraw their stake
* @param _amount amount of staked tokens requesting to withdraw
*/
function requestStakingWithdraw(uint256 _amount) external {
StakeInfo storage _staker = stakerDetails[msg.sender];
require(
_staker.stakedBalance >= _amount,
"insufficient staked balance"
);
_staker.startDate = block.timestamp;
_staker.lockedBalance += _amount;
_staker.stakedBalance -= _amount;
emit StakeWithdrawRequested(msg.sender, _amount);
}
/**
* @dev Withdraws a reporter's stake
*/
function withdrawStake() external {
StakeInfo storage _s = stakerDetails[msg.sender];
// Ensure reporter is locked and that enough time has passed
require(block.timestamp - _s.startDate >= 7 days, "7 days didn't pass");
require(_s.lockedBalance > 0, "reporter not locked for withdrawal");
_transfer(address(this), msg.sender, _s.lockedBalance);
_s.lockedBalance = 0;
emit StakeWithdrawn(msg.sender);
}
/**
* @dev Returns the reporter for a given timestamp and queryId
* @param _queryId bytes32 version of the queryId
* @param _timestamp uint256 timestamp of report
* @return address of data reporter
*/
function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp)
external
view
returns (address)
{
return reporterByTimestamp[_queryId][_timestamp];
}
/**
* @dev Allows users to retrieve all information about a staker
* @param _staker address of staker inquiring about
* @return uint startDate of staking
* @return uint current amount staked
* @return uint current amount locked for withdrawal
* @return uint reporter's last reported timestamp
* @return uint total number of reports submitted by reporter
*/
function getStakerInfo(address _staker)
external
view
returns (
uint256,
uint256,
uint256,
uint256,
uint256
)
{
return (
stakerDetails[_staker].startDate,
stakerDetails[_staker].stakedBalance,
stakerDetails[_staker].lockedBalance,
stakerDetails[_staker].reporterLastTimestamp,
stakerDetails[_staker].reportsSubmitted
);
}
// Getters
/**
* @dev Returns the amount that an address is alowed to spend of behalf of another
* @param _owner The address which owns the tokens
* @param _spender The address that will use the tokens
* @return uint256 The amount of allowed tokens
*/
function allowance(address _owner, address _spender)
public
view
virtual
returns (uint256)
{
return _allowances[_owner][_spender];
}
/**
* @dev Returns the balance of a given user.
* @param _account user address
* @return uint256 user's token balance
*/
function balanceOf(address _account) public view returns (uint256) {
return _balances[_account];
}
/**
* @dev Returns the number of decimals used to get its user representation.
* @return uint8 the number of decimals; used only for display purposes
*/
function decimals() public view returns (uint8) {
return _decimals;
}
/**
* @dev Calculates the current reward for a reporter given tips and time based reward
* @param _queryId is ID of the specific data feed
* @return uint256 tip amount for given query ID
* @return uint256 time based reward
*/
// slither-disable-next-line timestamp
function getCurrentReward(bytes32 _queryId)
public
view
returns (uint256, uint256)
{
uint256 _timeDiff = block.timestamp - timeOfLastNewValue;
uint256 _reward = (_timeDiff * timeBasedReward) / 300; //.5 TRB per 5 minutes (should we make this upgradeable)
if (balanceOf(address(this)) < _reward + tipsInContract) {
_reward = balanceOf(address(this)) - tipsInContract;
}
return (tips[_queryId], _reward);
}
/**
* @dev Counts the number of values that have been submitted for a given ID
* @param _queryId the ID to look up
* @return uint256 count of the number of values received for the queryId
*/
function getNewValueCountbyQueryId(bytes32 _queryId)
public
view
returns (uint256)
{
return timestamps[_queryId].length;
}
/**
* @dev Gets the timestamp for the value based on their index
* @param _queryId is the queryId to look up
* @param _index is the value index to look up
* @return uint256 timestamp
*/
function getTimestampbyQueryIdandIndex(bytes32 _queryId, uint256 _index)
public
view
returns (uint256)
{
uint256 len = timestamps[_queryId].length;
if (len == 0 || len <= _index) return 0;
return timestamps[_queryId][_index];
}
/**
* @dev Returns an array of voting rounds for a given vote
* @param _hash is the identifier hash for a vote
* @return uint256[] memory dispute IDs of the vote rounds
*/
function getVoteRounds(bytes32 _hash)
public
view
returns (uint256[] memory)
{
return voteRounds[_hash];
}
/**
* @dev Returns the governance address of the contract
* @return address (this address)
*/
function governance() external view returns(address){
return address(this);
}
/**
* @dev Returns the name of the token.
* @return string name of the token
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Retrieves value from oracle based on queryId/timestamp
* @param _queryId being requested
* @param _timestamp to retrieve data/value from
* @return bytes value for queryId/timestamp submitted
*/
function retrieveData(bytes32 _queryId, uint256 _timestamp)
public
view
returns (bytes memory)
{
return values[_queryId][_timestamp];
}
/**
* @dev Returns the symbol of the token.
* @return string symbol of the token
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the total supply of the token.
* @return uint256 total supply of token
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
// Internal functions
/**
* @dev Internal function to approve tokens for the user
* @param _owner The owner of the tokens
* @param _spender The address which is allowed to spend the tokens
* @param _amount The amount that msg.sender is allowing spender to use
*/
function _approve(
address _owner,
address _spender,
uint256 _amount
) internal virtual {
require(_owner != address(0), "ERC20: approve from the zero address");
require(_spender != address(0), "ERC20: approve to the zero address");
_allowances[_owner][_spender] = _amount;
emit Approval(_owner, _spender, _amount);
}
/**
* @dev Internal function to burn tokens for the user
* @param _account The address whose tokens to burn
* @param _amount The quantity of tokens to burn
*/
function _burn(address _account, uint256 _amount) internal virtual {
require(_account != address(0), "ERC20: burn from the zero address");
_balances[_account] -= _amount;
_totalSupply -= _amount;
emit Transfer(_account, address(0), _amount);
}
/**
* @dev Internal function to create new tokens for the user
* @param _account The address which receives minted tokens
* @param _amount The quantity of tokens to min
*/
function _mint(address _account, uint256 _amount) internal virtual {
require(_account != address(0), "ERC20: mint to the zero address");
_totalSupply += _amount;
_balances[_account] += _amount;
emit Transfer(address(0), _account, _amount);
}
/**
* @dev Internal function to perform token transfer
* @param _sender The address which owns the tokens
* @param _recipient The destination address
* @param _amount The quantity of tokens to transfer
*/
function _transfer(
address _sender,
address _recipient,
uint256 _amount
) internal virtual {
require(_sender != address(0), "ERC20: transfer from the zero address");
require(
_recipient != address(0),
"ERC20: transfer to the zero address"
);
_balances[_sender] -= _amount;
_balances[_recipient] += _amount;
emit Transfer(_sender, _recipient, _amount);
}
/**
* @dev Allows this contract to transfer tokens from one user to another
* @param _sender The address which owns the tokens
* @param _recipient The destination address
* @param _amount The quantity of tokens to transfer
* @return bool Whether the transfer succeeded
*/
function _transferFrom(
address _sender,
address _recipient,
uint256 _amount
) internal virtual returns (bool) {
_transfer(_sender, _recipient, _amount);
_approve(
_sender,
msg.sender,
_allowances[_sender][address(this)] - _amount
);
return true;
}
}