This repository has been archived by the owner on Nov 22, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
KnsTokenMining.sol
365 lines (315 loc) · 12.7 KB
/
KnsTokenMining.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
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./IMintableERC20.sol";
import "./KnsTokenWork.sol";
contract KnsTokenMining
is AccessControl,
KnsTokenWork
{
IMintableERC20 public token;
mapping (uint256 => uint256) private user_pow_height;
uint256 public constant ONE_KNS = 100000000;
uint256 public constant MINEABLE_TOKENS = 100 * 1000000 * ONE_KNS;
uint256 public constant FINAL_PRINT_RATE = 1500; // basis points
uint256 public constant TOTAL_EMISSION_TIME = 180 days;
uint256 public constant EMISSION_COEFF_1 = (MINEABLE_TOKENS * (20000 - FINAL_PRINT_RATE) * TOTAL_EMISSION_TIME);
uint256 public constant EMISSION_COEFF_2 = (MINEABLE_TOKENS * (10000 - FINAL_PRINT_RATE));
uint256 public constant HC_RESERVE_DECAY_TIME = 5 days;
uint256 public constant RECENT_BLOCK_LIMIT = 96;
uint256 public start_time;
uint256 public token_reserve;
uint256 public hc_reserve;
uint256 public last_mint_time;
bool public is_testing;
event Mine( address[] recipients, uint256[] split_percents, uint256 hc_submit, uint256 hc_decay, uint256 token_virtual_mint, uint256[] tokens_mined );
constructor( address tok, uint256 start_t, uint256 start_hc_reserve, bool testing )
public
{
token = IMintableERC20(tok);
_setupRole( DEFAULT_ADMIN_ROLE, _msgSender() );
start_time = start_t;
last_mint_time = start_t;
hc_reserve = start_hc_reserve;
token_reserve = 0;
is_testing = testing;
_initial_mining_event( start_hc_reserve );
}
function _initial_mining_event( uint256 start_hc_reserve ) internal
{
address[] memory recipients = new address[](1);
uint256[] memory split_percents = new uint256[](1);
uint256[] memory tokens_mined = new uint256[](1);
recipients[0] = address(0);
split_percents[0] = 10000;
tokens_mined[0] = 0;
emit Mine( recipients, split_percents, start_hc_reserve, 0, 0, tokens_mined );
}
/**
* Get the hash of the secured struct.
*
* Basically calls keccak256() on parameters. Mainly exists for readability purposes.
*/
function get_secured_struct_hash(
address[] memory recipients,
uint256[] memory split_percents,
uint256 recent_eth_block_number,
uint256 recent_eth_block_hash,
uint256 target,
uint256 pow_height
) public pure returns (uint256)
{
return uint256( keccak256( abi.encode( recipients, split_percents, recent_eth_block_number, recent_eth_block_hash, target, pow_height ) ) );
}
/**
* Require w[0]..w[9] are all distinct values.
*
* w[10] is untouched.
*/
function check_uniqueness(
uint256[11] memory w
) public pure
{
// Implement a simple direct comparison algorithm, unroll to optimize gas usage.
require( (w[0] != w[1]) && (w[0] != w[2]) && (w[0] != w[3]) && (w[0] != w[4]) && (w[0] != w[5]) && (w[0] != w[6]) && (w[0] != w[7]) && (w[0] != w[8]) && (w[0] != w[9])
&& (w[1] != w[2]) && (w[1] != w[3]) && (w[1] != w[4]) && (w[1] != w[5]) && (w[1] != w[6]) && (w[1] != w[7]) && (w[1] != w[8]) && (w[1] != w[9])
&& (w[2] != w[3]) && (w[2] != w[4]) && (w[2] != w[5]) && (w[2] != w[6]) && (w[2] != w[7]) && (w[2] != w[8]) && (w[2] != w[9])
&& (w[3] != w[4]) && (w[3] != w[5]) && (w[3] != w[6]) && (w[3] != w[7]) && (w[3] != w[8]) && (w[3] != w[9])
&& (w[4] != w[5]) && (w[4] != w[6]) && (w[4] != w[7]) && (w[4] != w[8]) && (w[4] != w[9])
&& (w[5] != w[6]) && (w[5] != w[7]) && (w[5] != w[8]) && (w[5] != w[9])
&& (w[6] != w[7]) && (w[6] != w[8]) && (w[6] != w[9])
&& (w[7] != w[8]) && (w[7] != w[9])
&& (w[8] != w[9]),
"Non-unique work components" );
}
/**
* Check proof of work for validity.
*
* Throws if the provided fields have any problems.
*/
function check_pow(
address[] memory recipients,
uint256[] memory split_percents,
uint256 recent_eth_block_number,
uint256 recent_eth_block_hash,
uint256 target,
uint256 pow_height,
uint256 nonce
) public view
{
require( recent_eth_block_hash != 0, "Zero block hash not allowed" );
require( recent_eth_block_number <= block.number, "Recent block in future" );
require( recent_eth_block_number + RECENT_BLOCK_LIMIT > block.number, "Recent block too old" );
require( nonce >= recent_eth_block_hash, "Nonce too small" );
require( (recent_eth_block_hash + (1 << 128)) > nonce, "Nonce too large" );
require( uint256( blockhash( recent_eth_block_number ) ) == recent_eth_block_hash, "Block hash mismatch" );
require( recipients.length <= 5, "Number of recipients cannot exceed 5" );
require( recipients.length == split_percents.length, "Recipient and split percent array size mismatch" );
array_check( split_percents );
require( get_pow_height( _msgSender(), recipients, split_percents ) + 1 == pow_height, "pow_height mismatch" );
uint256 h = get_secured_struct_hash( recipients, split_percents, recent_eth_block_number, recent_eth_block_hash, target, pow_height );
uint256[11] memory w = work( recent_eth_block_hash, h, nonce );
check_uniqueness( w );
require( w[10] < target, "Work missed target" ); // always fails if target == 0
}
function array_check( uint256[] memory arr )
internal pure
{
uint256 sum = 0;
for (uint i = 0; i < arr.length; i++)
{
require( arr[i] <= 10000, "Percent array element cannot exceed 10000" );
sum += arr[i];
}
require( sum == 10000, "Split percentages do not add up to 10000" );
}
function get_emission_curve( uint256 t )
public view returns (uint256)
{
if( t < start_time )
t = start_time;
if( t > start_time + TOTAL_EMISSION_TIME )
t = start_time + TOTAL_EMISSION_TIME;
t -= start_time;
return ((EMISSION_COEFF_1 - (EMISSION_COEFF_2*t))*t) / (10000 * TOTAL_EMISSION_TIME * TOTAL_EMISSION_TIME);
}
function get_hc_reserve_multiplier( uint256 dt )
public pure returns (uint256)
{
if( dt >= HC_RESERVE_DECAY_TIME )
return 0x80000000;
int256 idt = (int256( dt ) << 32) / int32(HC_RESERVE_DECAY_TIME);
int256 y = -0xa2b23f3;
y *= idt;
y >>= 32;
y += 0x3b9d3bec;
y *= idt;
y >>= 32;
y -= 0xb17217f7;
y *= idt;
y >>= 32;
y += 0x100000000;
if( y < 0 )
y = 0;
return uint256( y );
}
function get_background_activity( uint256 current_time ) public view
returns (uint256 hc_decay, uint256 token_virtual_mint)
{
hc_decay = 0;
token_virtual_mint = 0;
if( current_time <= last_mint_time )
return (hc_decay, token_virtual_mint);
uint256 dt = current_time - last_mint_time;
uint256 f_prev = get_emission_curve( last_mint_time );
uint256 f_now = get_emission_curve( current_time );
if( f_now <= f_prev )
return (hc_decay, token_virtual_mint);
uint256 mul = get_hc_reserve_multiplier( dt );
uint256 new_hc_reserve = (hc_reserve * mul) >> 32;
hc_decay = hc_reserve - new_hc_reserve;
token_virtual_mint = f_now - f_prev;
return (hc_decay, token_virtual_mint);
}
function process_background_activity( uint256 current_time ) internal
returns (uint256 hc_decay, uint256 token_virtual_mint)
{
(hc_decay, token_virtual_mint) = get_background_activity( current_time );
hc_reserve -= hc_decay;
token_reserve += token_virtual_mint;
last_mint_time = current_time;
return (hc_decay, token_virtual_mint);
}
/**
* Calculate value in tokens the given hash credits are worth
**/
function get_hash_credits_conversion( uint256 hc )
public view
returns (uint256)
{
require( hc > 1, "HC underflow" );
require( hc < (1 << 128), "HC overflow" );
// xyk algorithm
uint256 x0 = token_reserve;
uint256 y0 = hc_reserve;
require( x0 < (1 << 128), "Token balance overflow" );
require( y0 < (1 << 128), "HC balance overflow" );
uint256 y1 = y0 + hc;
require( y1 < (1 << 128), "HC balance overflow" );
// x0*y0 = x1*y1 -> x1 = (x0*y0)/y1
// NB above require() ensures overflow safety
uint256 x1 = ((x0*y0)/y1)+1;
require( x1 < x0, "No tokens available" );
return x0-x1;
}
/**
* Executes the trade of hash credits to tokens
* Returns number of minted tokens
**/
function convert_hash_credits(
uint256 hc ) internal
returns (uint256)
{
uint256 tokens_minted = get_hash_credits_conversion( hc );
hc_reserve += hc;
token_reserve -= tokens_minted;
return tokens_minted;
}
function increment_pow_height(
address[] memory recipients,
uint256[] memory split_percents ) internal
{
user_pow_height[uint256( keccak256( abi.encode( _msgSender(), recipients, split_percents ) ) )] += 1;
}
function mine_impl(
address[] memory recipients,
uint256[] memory split_percents,
uint256 recent_eth_block_number,
uint256 recent_eth_block_hash,
uint256 target,
uint256 pow_height,
uint256 nonce,
uint256 current_time ) internal
{
check_pow(
recipients,
split_percents,
recent_eth_block_number,
recent_eth_block_hash,
target,
pow_height,
nonce
);
uint256 hc_submit = uint256(-1)/target;
uint256 hc_decay;
uint256 token_virtual_mint;
(hc_decay, token_virtual_mint) = process_background_activity( current_time );
uint256 token_mined;
token_mined = convert_hash_credits( hc_submit );
uint256[] memory distribution = distribute( recipients, split_percents, token_mined );
increment_pow_height( recipients, split_percents );
emit Mine( recipients, split_percents, hc_submit, hc_decay, token_virtual_mint, distribution );
}
/**
* Get the total number of proof-of-work submitted by a user.
*/
function get_pow_height(
address from,
address[] memory recipients,
uint256[] memory split_percents
)
public view
returns (uint256)
{
return user_pow_height[uint256( keccak256( abi.encode( from, recipients, split_percents ) ) )];
}
/**
* Executes the distribution, minting the tokens to the recipient addresses
**/
function distribute(address[] memory recipients, uint256[] memory split_percents, uint256 token_mined)
internal returns ( uint256[] memory )
{
uint256 remaining = token_mined;
uint256[] memory distribution = new uint256[]( recipients.length );
for (uint i = distribution.length-1; i > 0; i--)
{
distribution[i] = (token_mined * split_percents[i]) / 10000;
token.mint( recipients[i], distribution[i] );
remaining -= distribution[i];
}
distribution[0] = remaining;
token.mint( recipients[0], remaining );
return distribution;
}
function mine(
address[] memory recipients,
uint256[] memory split_percents,
uint256 recent_eth_block_number,
uint256 recent_eth_block_hash,
uint256 target,
uint256 pow_height,
uint256 nonce ) public
{
require( now >= start_time, "Mining has not started" );
mine_impl( recipients, split_percents, recent_eth_block_number, recent_eth_block_hash, target, pow_height, nonce, now );
}
function test_process_background_activity( uint256 current_time )
public
{
require( is_testing, "Cannot call test method" );
process_background_activity( current_time );
}
function test_mine(
address[] memory recipients,
uint256[] memory split_percents,
uint256 recent_eth_block_number,
uint256 recent_eth_block_hash,
uint256 target,
uint256 pow_height,
uint256 nonce,
uint256 current_time ) public
{
require( is_testing, "Cannot call test method" );
mine_impl( recipients, split_percents, recent_eth_block_number, recent_eth_block_hash, target, pow_height, nonce, current_time );
}
}